├── .editorconfig ├── .env.example ├── .gitattributes ├── .gitignore ├── LICENSE ├── README.md ├── app ├── Http │ ├── Controllers │ │ ├── Auth │ │ │ ├── AuthenticatedSessionController.php │ │ │ ├── ConfirmablePasswordController.php │ │ │ ├── EmailVerificationNotificationController.php │ │ │ ├── EmailVerificationPromptController.php │ │ │ ├── NewPasswordController.php │ │ │ ├── PasswordController.php │ │ │ ├── PasswordResetLinkController.php │ │ │ ├── RegisteredUserController.php │ │ │ └── VerifyEmailController.php │ │ ├── ChequeController.php │ │ ├── CollectionController.php │ │ ├── ContactController.php │ │ ├── Controller.php │ │ ├── DashboardController.php │ │ ├── EmployeeBalanceController.php │ │ ├── EmployeeController.php │ │ ├── ExpenseController.php │ │ ├── MediaController.php │ │ ├── POSController.php │ │ ├── ProductController.php │ │ ├── ProfileController.php │ │ ├── PurchaseController.php │ │ ├── QuantityController.php │ │ ├── QuotationController.php │ │ ├── ReloadController.php │ │ ├── ReportController.php │ │ ├── SalaryRecordController.php │ │ ├── SaleController.php │ │ ├── SettingController.php │ │ ├── StoreController.php │ │ ├── TransactionController.php │ │ ├── UpgradeController.php │ │ └── UserController.php │ ├── Middleware │ │ └── HandleInertiaRequests.php │ └── Requests │ │ ├── Auth │ │ └── LoginRequest.php │ │ └── ProfileUpdateRequest.php ├── Models │ ├── Attachment.php │ ├── CashLog.php │ ├── Cheque.php │ ├── Collection.php │ ├── Contact.php │ ├── Employee.php │ ├── EmployeeBalanceLog.php │ ├── Expense.php │ ├── LoyaltyPointTransaction.php │ ├── Product.php │ ├── ProductBatch.php │ ├── ProductStock.php │ ├── Purchase.php │ ├── PurchaseItem.php │ ├── PurchaseTransaction.php │ ├── QuantityAdjustment.php │ ├── Quotation.php │ ├── QuotationItem.php │ ├── ReloadAndBillMeta.php │ ├── SalaryAdjustment.php │ ├── SalaryRecord.php │ ├── Sale.php │ ├── SaleItem.php │ ├── Setting.php │ ├── Store.php │ ├── Transaction.php │ └── User.php ├── Notifications │ ├── SaleCreated.php │ └── SaleDeleted.php ├── Providers │ └── AppServiceProvider.php └── Traits │ └── Userstamps.php ├── artisan ├── bootstrap ├── app.php ├── cache │ └── .gitignore └── providers.php ├── composer.json ├── composer.lock ├── config ├── app.php ├── auth.php ├── authentication-log.php ├── cache.php ├── database.php ├── filesystems.php ├── geoip.php ├── log-viewer.php ├── logging.php ├── mail.php ├── permission.php ├── queue.php ├── services.php ├── session.php └── version.php ├── database.sql ├── database ├── .gitignore ├── factories │ └── UserFactory.php ├── migrations │ ├── 2024_12_04_045251_create_db_infoshop_table.php │ └── 2025_03_02_201045_add_deleted_by_to_sales_table.php └── seeders │ ├── ContactSeeder.php │ ├── DatabaseSeeder.php │ ├── ProductSeeder.php │ └── SettingSeeder.php ├── git commands to create archive.txt ├── jsconfig.json ├── lang └── vendor │ └── authentication-log │ ├── en.json │ ├── fr.json │ └── pt_BR.json ├── package-lock.json ├── package.json ├── phpunit.xml ├── postcss.config.js ├── public ├── .htaccess ├── Infoshop-icon.png ├── css │ └── custom.css ├── index.php ├── oneshop-logo.png ├── robots.txt ├── tinymce │ ├── icons │ │ └── default │ │ │ └── icons.min.js │ ├── langs │ │ └── README.md │ ├── license.md │ ├── models │ │ └── dom │ │ │ └── model.min.js │ ├── plugins │ │ ├── accordion │ │ │ └── plugin.min.js │ │ ├── advlist │ │ │ └── plugin.min.js │ │ ├── anchor │ │ │ └── plugin.min.js │ │ ├── autolink │ │ │ └── plugin.min.js │ │ ├── autoresize │ │ │ └── plugin.min.js │ │ ├── autosave │ │ │ └── plugin.min.js │ │ ├── charmap │ │ │ └── plugin.min.js │ │ ├── code │ │ │ └── plugin.min.js │ │ ├── codesample │ │ │ └── plugin.min.js │ │ ├── directionality │ │ │ └── plugin.min.js │ │ ├── emoticons │ │ │ ├── js │ │ │ │ ├── emojiimages.js │ │ │ │ ├── emojiimages.min.js │ │ │ │ ├── emojis.js │ │ │ │ └── emojis.min.js │ │ │ └── plugin.min.js │ │ ├── fullscreen │ │ │ └── plugin.min.js │ │ ├── help │ │ │ ├── js │ │ │ │ └── i18n │ │ │ │ │ └── keynav │ │ │ │ │ ├── ar.js │ │ │ │ │ ├── bg_BG.js │ │ │ │ │ ├── ca.js │ │ │ │ │ ├── cs.js │ │ │ │ │ ├── da.js │ │ │ │ │ ├── de.js │ │ │ │ │ ├── el.js │ │ │ │ │ ├── en.js │ │ │ │ │ ├── es.js │ │ │ │ │ ├── eu.js │ │ │ │ │ ├── fa.js │ │ │ │ │ ├── fi.js │ │ │ │ │ ├── fr_FR.js │ │ │ │ │ ├── he_IL.js │ │ │ │ │ ├── hi.js │ │ │ │ │ ├── hr.js │ │ │ │ │ ├── hu_HU.js │ │ │ │ │ ├── id.js │ │ │ │ │ ├── it.js │ │ │ │ │ ├── ja.js │ │ │ │ │ ├── kk.js │ │ │ │ │ ├── ko_KR.js │ │ │ │ │ ├── ms.js │ │ │ │ │ ├── nb_NO.js │ │ │ │ │ ├── nl.js │ │ │ │ │ ├── pl.js │ │ │ │ │ ├── pt_BR.js │ │ │ │ │ ├── pt_PT.js │ │ │ │ │ ├── ro.js │ │ │ │ │ ├── ru.js │ │ │ │ │ ├── sk.js │ │ │ │ │ ├── sl_SI.js │ │ │ │ │ ├── sv_SE.js │ │ │ │ │ ├── th_TH.js │ │ │ │ │ ├── tr.js │ │ │ │ │ ├── uk.js │ │ │ │ │ ├── vi.js │ │ │ │ │ ├── zh_CN.js │ │ │ │ │ └── zh_TW.js │ │ │ └── plugin.min.js │ │ ├── image │ │ │ └── plugin.min.js │ │ ├── importcss │ │ │ └── plugin.min.js │ │ ├── insertdatetime │ │ │ └── plugin.min.js │ │ ├── link │ │ │ └── plugin.min.js │ │ ├── lists │ │ │ └── plugin.min.js │ │ ├── media │ │ │ └── plugin.min.js │ │ ├── nonbreaking │ │ │ └── plugin.min.js │ │ ├── pagebreak │ │ │ └── plugin.min.js │ │ ├── preview │ │ │ └── plugin.min.js │ │ ├── quickbars │ │ │ └── plugin.min.js │ │ ├── save │ │ │ └── plugin.min.js │ │ ├── searchreplace │ │ │ └── plugin.min.js │ │ ├── table │ │ │ └── plugin.min.js │ │ ├── visualblocks │ │ │ └── plugin.min.js │ │ ├── visualchars │ │ │ └── plugin.min.js │ │ └── wordcount │ │ │ └── plugin.min.js │ ├── skins │ │ ├── content │ │ │ ├── dark │ │ │ │ ├── content.js │ │ │ │ └── content.min.css │ │ │ ├── default │ │ │ │ ├── content.js │ │ │ │ └── content.min.css │ │ │ ├── document │ │ │ │ ├── content.js │ │ │ │ └── content.min.css │ │ │ ├── tinymce-5-dark │ │ │ │ ├── content.js │ │ │ │ └── content.min.css │ │ │ ├── tinymce-5 │ │ │ │ ├── content.js │ │ │ │ └── content.min.css │ │ │ └── writer │ │ │ │ ├── content.js │ │ │ │ └── content.min.css │ │ └── ui │ │ │ ├── oxide-dark │ │ │ ├── content.inline.js │ │ │ ├── content.inline.min.css │ │ │ ├── content.js │ │ │ ├── content.min.css │ │ │ ├── skin.js │ │ │ ├── skin.min.css │ │ │ ├── skin.shadowdom.js │ │ │ └── skin.shadowdom.min.css │ │ │ ├── oxide │ │ │ ├── content.inline.js │ │ │ ├── content.inline.min.css │ │ │ ├── content.js │ │ │ ├── content.min.css │ │ │ ├── skin.js │ │ │ ├── skin.min.css │ │ │ ├── skin.shadowdom.js │ │ │ └── skin.shadowdom.min.css │ │ │ ├── tinymce-5-dark │ │ │ ├── content.inline.js │ │ │ ├── content.inline.min.css │ │ │ ├── content.js │ │ │ ├── content.min.css │ │ │ ├── skin.js │ │ │ ├── skin.min.css │ │ │ ├── skin.shadowdom.js │ │ │ └── skin.shadowdom.min.css │ │ │ └── tinymce-5 │ │ │ ├── content.inline.js │ │ │ ├── content.inline.min.css │ │ │ ├── content.js │ │ │ ├── content.min.css │ │ │ ├── skin.js │ │ │ ├── skin.min.css │ │ │ ├── skin.shadowdom.js │ │ │ └── skin.shadowdom.min.css │ ├── themes │ │ └── silver │ │ │ └── theme.min.js │ ├── tinymce.d.ts │ └── tinymce.min.js └── vendor │ └── log-viewer │ ├── app.css │ ├── app.js │ ├── app.js.LICENSE.txt │ ├── img │ ├── log-viewer-128.png │ ├── log-viewer-32.png │ └── log-viewer-64.png │ └── mix-manifest.json ├── resources ├── css │ └── app.css ├── js │ ├── Components │ │ ├── AddPaymentDialog.jsx │ │ ├── CustomPagination.jsx │ │ ├── DangerButton.jsx │ │ ├── InputError.jsx │ │ ├── InputLabel.jsx │ │ ├── Modal.jsx │ │ ├── PaymentsCheckoutDialog.jsx │ │ ├── PrimaryButton.jsx │ │ ├── SecondaryButton.jsx │ │ ├── TextInput.jsx │ │ ├── TinyMCEEditor.jsx │ │ └── ViewDetailsDialog.jsx │ ├── Context │ │ ├── CartContext.jsx │ │ ├── PurchaseContext.jsx │ │ ├── SalesContext.jsx │ │ ├── SharedContext.jsx │ │ └── useCartBase.js │ ├── Infomax-logo.png │ ├── Layouts │ │ ├── AuthenticatedLayout.jsx │ │ └── GuestLayout.jsx │ ├── Pages │ │ ├── Auth │ │ │ ├── ConfirmPassword.jsx │ │ │ ├── ForgotPassword.jsx │ │ │ ├── Login.jsx │ │ │ ├── Register.jsx │ │ │ ├── ResetPassword.jsx │ │ │ └── VerifyEmail.jsx │ │ ├── BlockEditor │ │ │ ├── Editor.jsx │ │ │ └── LeftSideBar.jsx │ │ ├── Cheque │ │ │ ├── Cheque.jsx │ │ │ └── ChequeFormDialog.jsx │ │ ├── Collection │ │ │ ├── Collection.jsx │ │ │ └── Partial │ │ │ │ └── FormDialog.jsx │ │ ├── Contact │ │ │ ├── Contact.jsx │ │ │ └── Partial │ │ │ │ └── FormDialog.jsx │ │ ├── Dashboard │ │ │ ├── Dashboard.jsx │ │ │ └── Partials │ │ │ │ └── Summaries.jsx │ │ ├── Employee │ │ │ ├── Employee.jsx │ │ │ ├── EmployeeReport.jsx │ │ │ └── Partials │ │ │ │ ├── EmployeeBalanceDialog.jsx │ │ │ │ ├── EmployeeDialog.jsx │ │ │ │ └── SalaryFormDialog.jsx │ │ ├── Expense │ │ │ ├── Expense.jsx │ │ │ └── Partials │ │ │ │ └── ExpenseDialog.jsx │ │ ├── Media │ │ │ └── Media.jsx │ │ ├── POS │ │ │ ├── CustomerDisplay.jsx │ │ │ ├── POS.jsx │ │ │ ├── Partial │ │ │ │ ├── CartFooter.jsx │ │ │ │ ├── CartIcon.jsx │ │ │ │ ├── CartItem.jsx │ │ │ │ ├── CartItemModal.jsx │ │ │ │ ├── CartItemsTop.jsx │ │ │ │ ├── CartSummary.jsx │ │ │ │ ├── CashCheckoutDialog.jsx │ │ │ │ ├── HeldItemsModal.jsx │ │ │ │ ├── POSBottomBar.jsx │ │ │ │ ├── ProductItem.jsx │ │ │ │ ├── QuantityInput.css │ │ │ │ ├── QuantityInput.jsx │ │ │ │ ├── QuotationDialog.jsx │ │ │ │ └── SearchBox.jsx │ │ │ └── ProductTypes │ │ │ │ └── Commission.jsx │ │ ├── Payment │ │ │ └── Payment.jsx │ │ ├── Payroll │ │ │ └── Payroll.jsx │ │ ├── Product │ │ │ ├── Barcode.jsx │ │ │ ├── BarcodeView.jsx │ │ │ ├── Partials │ │ │ │ ├── BatchModal.jsx │ │ │ │ └── QuantityModal.jsx │ │ │ ├── Product.jsx │ │ │ ├── ProductForm.jsx │ │ │ └── product-placeholder.webp │ │ ├── Profile │ │ │ ├── Edit.jsx │ │ │ └── Partials │ │ │ │ ├── DeleteUserForm.jsx │ │ │ │ ├── UpdatePasswordForm.jsx │ │ │ │ └── UpdateProfileInformationForm.jsx │ │ ├── Purchase │ │ │ ├── Purchase.jsx │ │ │ └── PurchaseForm │ │ │ │ ├── AddToPurchase.jsx │ │ │ │ ├── ProductSearch.jsx │ │ │ │ ├── PurchaseAppBar.jsx │ │ │ │ ├── PurchaseCartItems.jsx │ │ │ │ └── PurchaseForm.jsx │ │ ├── Quotation │ │ │ ├── Quotation.jsx │ │ │ └── QuotationView.jsx │ │ ├── Reload │ │ │ ├── Reload.jsx │ │ │ └── ReloadFormDialog.jsx │ │ ├── Report │ │ │ ├── ContactReport.jsx │ │ │ ├── DailyCashReport.jsx │ │ │ ├── Partial │ │ │ │ └── DailyCashDialog.jsx │ │ │ ├── SalesReport.jsx │ │ │ └── SummaryReport.jsx │ │ ├── Sale │ │ │ ├── Receipt.jsx │ │ │ └── Sale.jsx │ │ ├── Settings │ │ │ ├── BarcodeTemplate.jsx │ │ │ ├── CustomCSS.jsx │ │ │ ├── Partials │ │ │ │ ├── LoyaltyPointsSetting.jsx │ │ │ │ ├── MailSetting.jsx │ │ │ │ ├── MiscSetting.jsx │ │ │ │ ├── ModuleSetting.jsx │ │ │ │ ├── TelegramSetting.jsx │ │ │ │ └── Template.jsx │ │ │ └── Settings.jsx │ │ ├── SoldItem │ │ │ └── SoldItem.jsx │ │ ├── Store │ │ │ ├── Partial │ │ │ │ └── FormDialog.jsx │ │ │ └── Store.jsx │ │ ├── Update │ │ │ └── Update.jsx │ │ ├── User │ │ │ ├── User.jsx │ │ │ ├── UserFormDialog.jsx │ │ │ ├── UserRole.jsx │ │ │ └── UserRoleDialog.jsx │ │ └── Welcome.jsx │ ├── app.jsx │ ├── bootstrap.js │ └── infoshop.png └── views │ ├── app.blade.php │ ├── templates │ ├── barcode-template.html │ ├── barcode-xs-template.html │ ├── quote-template.html │ └── receipt-template.html │ ├── upload.blade.php │ └── vendor │ └── authentication-log │ ├── .gitkeep │ └── emails │ ├── failed.blade.php │ └── new.blade.php ├── routes ├── auth.php ├── console.php └── web.php ├── server config note.txt ├── storage ├── app │ ├── .gitignore │ ├── private │ │ └── .gitignore │ └── public │ │ └── .gitignore ├── framework │ ├── .gitignore │ ├── cache │ │ ├── .gitignore │ │ └── data │ │ │ └── .gitignore │ ├── sessions │ │ └── .gitignore │ ├── testing │ │ └── .gitignore │ └── views │ │ └── .gitignore └── logs │ └── .gitignore ├── tailwind.config.js ├── tests ├── Feature │ ├── Auth │ │ ├── AuthenticationTest.php │ │ ├── EmailVerificationTest.php │ │ ├── PasswordConfirmationTest.php │ │ ├── PasswordResetTest.php │ │ ├── PasswordUpdateTest.php │ │ └── RegistrationTest.php │ ├── ExampleTest.php │ └── ProfileTest.php ├── TestCase.php └── Unit │ └── ExampleTest.php └── vite.config.js /.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | [*] 4 | charset = utf-8 5 | end_of_line = lf 6 | indent_size = 4 7 | indent_style = space 8 | insert_final_newline = true 9 | trim_trailing_whitespace = true 10 | 11 | [*.md] 12 | trim_trailing_whitespace = false 13 | 14 | [*.{yml,yaml}] 15 | indent_size = 2 16 | 17 | [docker-compose.yml] 18 | indent_size = 4 19 | -------------------------------------------------------------------------------- /.env.example: -------------------------------------------------------------------------------- 1 | APP_NAME=Laravel 2 | APP_ENV=local 3 | APP_KEY= 4 | APP_DEBUG=true 5 | APP_TIMEZONE=UTC 6 | APP_URL=http://localhost 7 | 8 | APP_LOCALE=en 9 | APP_FALLBACK_LOCALE=en 10 | APP_FAKER_LOCALE=en_US 11 | 12 | APP_MAINTENANCE_DRIVER=file 13 | # APP_MAINTENANCE_STORE=database 14 | 15 | BCRYPT_ROUNDS=12 16 | 17 | LOG_CHANNEL=stack 18 | LOG_STACK=single 19 | LOG_DEPRECATIONS_CHANNEL=null 20 | LOG_LEVEL=debug 21 | 22 | DB_CONNECTION=mysql 23 | DB_HOST=127.0.0.1 24 | DB_PORT=3306 25 | DB_DATABASE=laravelreact 26 | DB_USERNAME=root 27 | DB_PASSWORD= 28 | 29 | SESSION_DRIVER=database 30 | SESSION_LIFETIME=120 31 | SESSION_ENCRYPT=false 32 | SESSION_PATH=/ 33 | SESSION_DOMAIN=null 34 | 35 | BROADCAST_CONNECTION=log 36 | FILESYSTEM_DISK=local 37 | QUEUE_CONNECTION=database 38 | 39 | CACHE_STORE=database 40 | CACHE_PREFIX= 41 | 42 | MEMCACHED_HOST=127.0.0.1 43 | 44 | REDIS_CLIENT=phpredis 45 | REDIS_HOST=127.0.0.1 46 | REDIS_PASSWORD=null 47 | REDIS_PORT=6379 48 | 49 | MAIL_MAILER=log 50 | MAIL_HOST=127.0.0.1 51 | MAIL_PORT=2525 52 | MAIL_USERNAME=null 53 | MAIL_PASSWORD=null 54 | MAIL_ENCRYPTION=null 55 | MAIL_FROM_ADDRESS="hello@example.com" 56 | MAIL_FROM_NAME="${APP_NAME}" 57 | 58 | AWS_ACCESS_KEY_ID= 59 | AWS_SECRET_ACCESS_KEY= 60 | AWS_DEFAULT_REGION=us-east-1 61 | AWS_BUCKET= 62 | AWS_USE_PATH_STYLE_ENDPOINT=false 63 | 64 | VITE_APP_NAME="${APP_NAME}" 65 | 66 | FILESYSTEM_DRIVER=public 67 | -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | * text=auto eol=lf 2 | 3 | *.blade.php diff=html 4 | *.css diff=css 5 | *.html diff=html 6 | *.md diff=markdown 7 | *.php diff=php 8 | 9 | /.github export-ignore 10 | CHANGELOG.md export-ignore 11 | .styleci.yml export-ignore 12 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /.phpunit.cache 2 | /node_modules 3 | /public/build 4 | /public/hot 5 | /public/storage 6 | /storage/*.key 7 | /vendor 8 | .env 9 | .env.backup 10 | .env.production 11 | .phpactor.json 12 | .phpunit.result.cache 13 | Homestead.json 14 | Homestead.yaml 15 | auth.json 16 | npm-debug.log 17 | yarn-error.log 18 | /.fleet 19 | /.idea 20 | /.vscode 21 | /.zed 22 | update.zip 23 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2025 Nifras Usanar 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 6 | 7 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 8 | 9 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -------------------------------------------------------------------------------- /app/Http/Controllers/Auth/AuthenticatedSessionController.php: -------------------------------------------------------------------------------- 1 | Route::has('password.request'), 24 | 'status' => session('status'), 25 | 'version'=>$version, 26 | ]); 27 | } 28 | 29 | /** 30 | * Handle an incoming authentication request. 31 | */ 32 | public function store(LoginRequest $request): RedirectResponse 33 | { 34 | $request->authenticate(); 35 | 36 | $request->session()->regenerate(); 37 | 38 | return redirect()->intended(route('dashboard', absolute: false)); 39 | } 40 | 41 | /** 42 | * Destroy an authenticated session. 43 | */ 44 | public function destroy(Request $request): RedirectResponse 45 | { 46 | Auth::guard('web')->logout(); 47 | 48 | $request->session()->invalidate(); 49 | 50 | $request->session()->regenerateToken(); 51 | 52 | return redirect('/'); 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /app/Http/Controllers/Auth/ConfirmablePasswordController.php: -------------------------------------------------------------------------------- 1 | validate([ 29 | 'email' => $request->user()->email, 30 | 'password' => $request->password, 31 | ])) { 32 | throw ValidationException::withMessages([ 33 | 'password' => __('auth.password'), 34 | ]); 35 | } 36 | 37 | $request->session()->put('auth.password_confirmed_at', time()); 38 | 39 | return redirect()->intended(route('dashboard', absolute: false)); 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /app/Http/Controllers/Auth/EmailVerificationNotificationController.php: -------------------------------------------------------------------------------- 1 | user()->hasVerifiedEmail()) { 17 | return redirect()->intended(route('dashboard', absolute: false)); 18 | } 19 | 20 | $request->user()->sendEmailVerificationNotification(); 21 | 22 | return back()->with('status', 'verification-link-sent'); 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /app/Http/Controllers/Auth/EmailVerificationPromptController.php: -------------------------------------------------------------------------------- 1 | user()->hasVerifiedEmail() 19 | ? redirect()->intended(route('dashboard', absolute: false)) 20 | : Inertia::render('Auth/VerifyEmail', ['status' => session('status')]); 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /app/Http/Controllers/Auth/NewPasswordController.php: -------------------------------------------------------------------------------- 1 | $request->email, 26 | 'token' => $request->route('token'), 27 | ]); 28 | } 29 | 30 | /** 31 | * Handle an incoming new password request. 32 | * 33 | * @throws \Illuminate\Validation\ValidationException 34 | */ 35 | public function store(Request $request): RedirectResponse 36 | { 37 | $request->validate([ 38 | 'token' => 'required', 39 | 'email' => 'required|email', 40 | 'password' => ['required', 'confirmed', Rules\Password::defaults()], 41 | ]); 42 | 43 | // Here we will attempt to reset the user's password. If it is successful we 44 | // will update the password on an actual user model and persist it to the 45 | // database. Otherwise we will parse the error and return the response. 46 | $status = Password::reset( 47 | $request->only('email', 'password', 'password_confirmation', 'token'), 48 | function ($user) use ($request) { 49 | $user->forceFill([ 50 | 'password' => Hash::make($request->password), 51 | 'remember_token' => Str::random(60), 52 | ])->save(); 53 | 54 | event(new PasswordReset($user)); 55 | } 56 | ); 57 | 58 | // If the password was successfully reset, we will redirect the user back to 59 | // the application's home authenticated view. If there is an error we can 60 | // redirect them back to where they came from with their error message. 61 | if ($status == Password::PASSWORD_RESET) { 62 | return redirect()->route('login')->with('status', __($status)); 63 | } 64 | 65 | throw ValidationException::withMessages([ 66 | 'email' => [trans($status)], 67 | ]); 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /app/Http/Controllers/Auth/PasswordController.php: -------------------------------------------------------------------------------- 1 | validate([ 19 | 'current_password' => ['required', 'current_password'], 20 | 'password' => ['required', Password::defaults(), 'confirmed'], 21 | ]); 22 | 23 | $request->user()->update([ 24 | 'password' => Hash::make($validated['password']), 25 | ]); 26 | 27 | return back(); 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /app/Http/Controllers/Auth/PasswordResetLinkController.php: -------------------------------------------------------------------------------- 1 | session('status'), 22 | ]); 23 | } 24 | 25 | /** 26 | * Handle an incoming password reset link request. 27 | * 28 | * @throws \Illuminate\Validation\ValidationException 29 | */ 30 | public function store(Request $request): RedirectResponse 31 | { 32 | $request->validate([ 33 | 'email' => 'required|email', 34 | ]); 35 | 36 | // We will send the password reset link to this user. Once we have attempted 37 | // to send the link, we will examine the response then see the message we 38 | // need to show to the user. Finally, we'll send out a proper response. 39 | $status = Password::sendResetLink( 40 | $request->only('email') 41 | ); 42 | 43 | if ($status == Password::RESET_LINK_SENT) { 44 | return back()->with('status', __($status)); 45 | } 46 | 47 | throw ValidationException::withMessages([ 48 | 'email' => [trans($status)], 49 | ]); 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /app/Http/Controllers/Auth/RegisteredUserController.php: -------------------------------------------------------------------------------- 1 | validate([ 34 | 'name' => 'required|string|max:255', 35 | 'email' => 'required|string|lowercase|email|max:255|unique:'.User::class, 36 | 'password' => ['required', 'confirmed', Rules\Password::defaults()], 37 | ]); 38 | 39 | $user = User::create([ 40 | 'name' => $request->name, 41 | 'email' => $request->email, 42 | 'password' => Hash::make($request->password), 43 | ]); 44 | 45 | event(new Registered($user)); 46 | 47 | Auth::login($user); 48 | 49 | return redirect(route('dashboard', absolute: false)); 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /app/Http/Controllers/Auth/VerifyEmailController.php: -------------------------------------------------------------------------------- 1 | user()->hasVerifiedEmail()) { 18 | return redirect()->intended(route('dashboard', absolute: false).'?verified=1'); 19 | } 20 | 21 | if ($request->user()->markEmailAsVerified()) { 22 | event(new Verified($request->user())); 23 | } 24 | 25 | return redirect()->intended(route('dashboard', absolute: false).'?verified=1'); 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /app/Http/Controllers/CollectionController.php: -------------------------------------------------------------------------------- 1 | get(); 16 | 17 | // Render the Inertia view with the collections data 18 | return Inertia::render('Collection/Collection', [ 19 | 'collections' => $collections, 20 | 'pageLabel'=>'Collections', 21 | ]); 22 | } 23 | 24 | public function store(Request $request) 25 | { 26 | $validatedData = $request->validate([ 27 | 'name' => 'required|string|unique:collections,name', // 'name' must be unique 28 | 'collection_type' => 'required|string|max:50', 29 | 'description' => 'nullable|string', 30 | ]); 31 | $validatedData['slug'] = Str::slug($request->input('name')); 32 | 33 | // 4. Save the data to the database 34 | Collection::create($validatedData); 35 | 36 | return redirect()->route('collection')->with('success', 'Collection created successfully!'); 37 | } 38 | 39 | public function update(Request $request, $id) 40 | { 41 | $collection = Collection::findOrFail($id); 42 | $validatedData = $request->validate([ 43 | 'name' => 'required|string|max:255|unique:collections,name,' . $id, 44 | 'collection_type' => 'required|string|max:50', 45 | 'description' => 'nullable|string', 46 | 'slug' => 'nullable|string|max:255|unique:collections,slug,' . $id, 47 | ]); 48 | $validatedData['slug'] = Str::slug($validatedData['name']); 49 | // 2. Update the data in the database 50 | $collection->update($validatedData); 51 | 52 | return redirect()->route('collection')->with('success', 'Collection updated successfully!'); 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /app/Http/Controllers/Controller.php: -------------------------------------------------------------------------------- 1 | orderBy('id', 'desc'); 15 | 16 | if(isset($filters['start_date']) && isset($filters['end_date'])){ 17 | $query->whereBetween('expense_date', [$filters['start_date'], $filters['end_date']]); 18 | } 19 | 20 | if (!empty($filters['search_query'])) { 21 | $query->where('description', 'like', "%{$filters['search_query']}%"); 22 | } 23 | 24 | $results = $query->paginate(25); 25 | $results->appends($filters); 26 | return $results; 27 | } 28 | 29 | public function index(Request $request){ 30 | $filters = $request->only(['start_date', 'end_date', 'search_query']); 31 | $stores = Store::select('id', 'name')->get(); 32 | $expenses = $this->getExpenses($filters); 33 | 34 | return Inertia::render('Expense/Expense', [ 35 | 'expenses' => $expenses, 36 | 'stores'=>$stores, 37 | 'pageLabel'=>'Expenses', 38 | ]); 39 | } 40 | 41 | public function store(Request $request){ 42 | $expense = new Expense(); 43 | $expense->description = $request->description; 44 | $expense->amount = $request->amount; 45 | $expense->expense_date = $request->expense_date; 46 | $expense->store_id = $request->store_id; 47 | $expense->source = $request->source; 48 | $expense->save(); 49 | 50 | return response()->json([ 51 | 'message'=>"Expense added successfully", 52 | ], 200); 53 | } 54 | 55 | public function delete($id) 56 | { 57 | $expense = Expense::find($id); 58 | 59 | if (!$expense) { 60 | return response()->json(['message' => 'Expense not found'], 404); 61 | } 62 | 63 | $expense->delete(); 64 | 65 | return response()->json(['message' => 'Expense deleted successfully'], 200); 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /app/Http/Controllers/ProfileController.php: -------------------------------------------------------------------------------- 1 | $request->user() instanceof MustVerifyEmail, 23 | 'status' => session('status'), 24 | 'pageLabel'=>'Profile', 25 | ]); 26 | } 27 | 28 | /** 29 | * Update the user's profile information. 30 | */ 31 | public function update(ProfileUpdateRequest $request): RedirectResponse 32 | { 33 | $request->user()->fill($request->validated()); 34 | 35 | if ($request->user()->isDirty('email')) { 36 | $request->user()->email_verified_at = null; 37 | } 38 | 39 | $request->user()->save(); 40 | 41 | return Redirect::route('profile.edit'); 42 | } 43 | 44 | /** 45 | * Delete the user's account. 46 | */ 47 | public function destroy(Request $request): RedirectResponse 48 | { 49 | $request->validate([ 50 | 'password' => ['required', 'current_password'], 51 | ]); 52 | 53 | $user = $request->user(); 54 | 55 | Auth::logout(); 56 | 57 | $user->delete(); 58 | 59 | $request->session()->invalidate(); 60 | $request->session()->regenerateToken(); 61 | 62 | return Redirect::to('/'); 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /app/Http/Controllers/QuantityController.php: -------------------------------------------------------------------------------- 1 | validate([ 19 | 'batch_id' => 'required', 20 | 'stock_id' => 'nullable|integer', // Can be null to create a new ProductStock 21 | 'quantity' => 'required', 22 | 'reason' => 'required|string', 23 | 'store_id' => 'required|integer', 24 | ]); 25 | 26 | // Start database transaction to ensure atomicity 27 | DB::beginTransaction(); 28 | 29 | try { 30 | // Attempt to find existing ProductStock by store_id and batch_id 31 | $productStock = ProductStock::where('store_id', $validated['store_id']) 32 | ->where('batch_id', $validated['batch_id']) 33 | ->first(); 34 | 35 | if ($productStock) { 36 | // If stock exists, update the quantity 37 | $previousQuantity = $productStock->quantity; 38 | $productStock->quantity += $validated['quantity']; 39 | $productStock->save(); 40 | } else { 41 | $product = ProductBatch::find($validated['batch_id']); 42 | // If stock doesn't exist, create a new ProductStock record 43 | $productStock = ProductStock::create([ 44 | 'store_id' => $validated['store_id'], 45 | 'batch_id' => $validated['batch_id'], 46 | 'quantity' => $validated['quantity'], 47 | 'product_id' => $product->product_id 48 | ]); 49 | 50 | // Since this is a new record, previous quantity will be 0 51 | $previousQuantity = 0; 52 | } 53 | 54 | // Create a new QuantityAdjustment record 55 | $quantityAdjustment = new QuantityAdjustment([ 56 | 'batch_id' => $validated['batch_id'], 57 | 'stock_id'=>$productStock->id, 58 | 'previous_quantity' => $previousQuantity, 59 | 'adjusted_quantity' => $validated['quantity'], 60 | 'reason' => $validated['reason'], 61 | ]); 62 | 63 | $quantityAdjustment->save(); 64 | 65 | // Commit transaction 66 | DB::commit(); 67 | 68 | // Return success response 69 | return response()->json(['message' => 'Quantity adjustment and stock update successful.'], 200); 70 | 71 | } catch (\Exception $e) { 72 | // Rollback transaction in case of error 73 | DB::rollBack(); 74 | 75 | // Return error response 76 | return response()->json(['error' => $e], 500); 77 | } 78 | } 79 | 80 | } 81 | -------------------------------------------------------------------------------- /app/Http/Middleware/HandleInertiaRequests.php: -------------------------------------------------------------------------------- 1 | 32 | */ 33 | public function share(Request $request): array 34 | { 35 | $permissions = collect(); 36 | if ($request->user()) { 37 | $user = $request->user(); 38 | $role = Role::where('name',$user->user_role)->first(); 39 | $permissions = $role->permissions; 40 | } 41 | 42 | $shopNameMeta = Setting::where('meta_key', 'shop_name')->first(); 43 | $modules = Setting::getModules(); 44 | return [ 45 | ...parent::share($request), 46 | 'auth' => [ 47 | 'user' => $request->user(), 48 | ], 49 | 'settings'=>[ 50 | 'shop_name'=>$shopNameMeta->meta_value, 51 | ], 52 | 'modules'=>$modules, 53 | 'userPermissions'=>$permissions->pluck('name'), 54 | ]; 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /app/Http/Requests/ProfileUpdateRequest.php: -------------------------------------------------------------------------------- 1 | 15 | */ 16 | public function rules(): array 17 | { 18 | return [ 19 | 'name' => ['required', 'string', 'max:255'], 20 | 'email' => ['required', 'string', 'lowercase', 'email', 'max:255', Rule::unique(User::class)->ignore($this->user()->id)], 21 | ]; 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /app/Models/Attachment.php: -------------------------------------------------------------------------------- 1 | belongsTo(Collection::class, 'parent_id'); 24 | } 25 | 26 | public function children() 27 | { 28 | return $this->hasMany(Collection::class, 'parent_id'); 29 | } 30 | 31 | // Accessor for formatted updated_at date 32 | public function getUpdatedAtAttribute($value) 33 | { 34 | return \Carbon\Carbon::parse($value)->format('Y-m-d'); // Adjust the format as needed 35 | } 36 | 37 | // Accessor for formatted created_at date 38 | public function getCreatedAtAttribute($value) 39 | { 40 | return \Carbon\Carbon::parse($value)->format('Y-m-d'); // Adjust the format as needed 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /app/Models/Contact.php: -------------------------------------------------------------------------------- 1 | get(); 29 | public function scopeCustomers($query) 30 | { 31 | return $query->where('type', 'customer'); 32 | } 33 | 34 | // Contact::vendors()->get(); 35 | public function scopeVendors($query) 36 | { 37 | return $query->where('type', 'vendor'); 38 | } 39 | 40 | // Accessor for formatted created_at date 41 | public function getCreatedAtAttribute($value) 42 | { 43 | return \Carbon\Carbon::parse($value)->format('Y-m-d'); // Adjust the format as needed 44 | } 45 | 46 | public function incrementBalance($amount, $user) 47 | { 48 | // Step 1: Get the current balance 49 | $previousBalance = $this->balance; 50 | 51 | // Step 2: Increment the balance 52 | $this->increment('balance', $amount); 53 | 54 | // Step 3: Log the activity with previous balance, new balance, and user context 55 | activity() 56 | ->performedOn($this) 57 | ->causedBy($user) 58 | ->withProperties([ 59 | 'previous_balance' => $previousBalance, 60 | 'incremented_amount' => $amount, 61 | 'new_balance' => $this->balance, 62 | ]) 63 | ->log('Increased balance from ' . $previousBalance . ' to ' . $this->balance); 64 | } 65 | 66 | public function quotations() { 67 | return $this->hasMany(Quotation::class); 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /app/Models/Employee.php: -------------------------------------------------------------------------------- 1 | 'date', 33 | ]; 34 | } 35 | -------------------------------------------------------------------------------- /app/Models/EmployeeBalanceLog.php: -------------------------------------------------------------------------------- 1 | format('Y-m-d'); // Adjust the format as needed 40 | } 41 | 42 | // Accessor for formatted created_at date 43 | public function getCreatedAtAttribute($value) 44 | { 45 | return \Carbon\Carbon::parse($value)->format('Y-m-d'); // Adjust the format as needed 46 | } 47 | 48 | protected $casts = [ 49 | 'meta_data' => 'array', // Ensure the meta_data column is treated as an array 50 | ]; 51 | } 52 | -------------------------------------------------------------------------------- /app/Models/ProductBatch.php: -------------------------------------------------------------------------------- 1 | hasMany(PurchaseTransaction::class, 'purchase_id'); 33 | } 34 | 35 | } 36 | -------------------------------------------------------------------------------- /app/Models/PurchaseItem.php: -------------------------------------------------------------------------------- 1 | belongsTo(Contact::class); 16 | } 17 | 18 | public function items() { 19 | return $this->hasMany(QuotationItem::class); 20 | } 21 | 22 | public function quotationItems() 23 | { 24 | return $this->hasMany(QuotationItem::class); 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /app/Models/QuotationItem.php: -------------------------------------------------------------------------------- 1 | belongsTo(Quotation::class); 11 | } 12 | 13 | public function product() { 14 | return $this->belongsTo(Product::class); 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /app/Models/ReloadAndBillMeta.php: -------------------------------------------------------------------------------- 1 | where('store_id', $storeId); 35 | } 36 | return $query; 37 | } 38 | 39 | public function scopeDateFilter($query, $startDate, $endDate) 40 | { 41 | if (!empty($startDate) && !empty($endDate)) { 42 | return $query->whereBetween('salary_date', [$startDate, $endDate]); 43 | } 44 | return $query; 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /app/Models/SaleItem.php: -------------------------------------------------------------------------------- 1 | belongsTo(Sale::class); 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /app/Models/Setting.php: -------------------------------------------------------------------------------- 1 | first(); 22 | if ($setting) { 23 | $decodedValue = json_decode($setting->meta_value, true); 24 | if (json_last_error() === JSON_ERROR_NONE) { 25 | return $decodedValue; 26 | } 27 | } 28 | return null; 29 | } 30 | 31 | /** 32 | * Retrieve modules as an array by splitting the comma-separated string 33 | * 34 | * @return array 35 | */ 36 | public static function getModules() 37 | { 38 | $setting = self::where('meta_key', 'modules')->first(); 39 | if ($setting) { 40 | return explode(',', $setting->meta_value); 41 | } 42 | return []; 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /app/Models/Store.php: -------------------------------------------------------------------------------- 1 | format('Y-m-d'); // Adjust the format as needed 29 | } 30 | 31 | // Accessor for formatted created_at date 32 | public function getCreatedAtAttribute($value) 33 | { 34 | return \Carbon\Carbon::parse($value)->format('Y-m-d'); // Adjust the format as needed 35 | } 36 | 37 | public function scopeForCurrentUser($query) 38 | { 39 | if (Auth::user()->user_role === 'admin' || Auth::user()->user_role === 'super-admin') { 40 | return $query; 41 | } 42 | 43 | return $query->where('id', Auth::user()->store_id); 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /app/Models/User.php: -------------------------------------------------------------------------------- 1 | 21 | */ 22 | protected $fillable = [ 23 | 'name', 24 | 'email', 25 | 'password', 26 | 'user_name', 27 | 'user_role', 28 | 'is_active', 29 | 'store_id', 30 | ]; 31 | 32 | /** 33 | * The attributes that should be hidden for serialization. 34 | * 35 | * @var array 36 | */ 37 | protected $hidden = [ 38 | 'password', 39 | 'remember_token', 40 | ]; 41 | 42 | /** 43 | * Get the attributes that should be cast. 44 | * 45 | * @return array 46 | */ 47 | protected function casts(): array 48 | { 49 | return [ 50 | 'email_verified_at' => 'datetime', 51 | 'password' => 'hashed', 52 | ]; 53 | } 54 | 55 | const ROLE_ADMIN = 'admin'; 56 | const ROLE_USER = 'user'; 57 | } 58 | -------------------------------------------------------------------------------- /app/Notifications/SaleDeleted.php: -------------------------------------------------------------------------------- 1 | botToken = $botToken; 25 | $this->sale = $sale; 26 | } 27 | 28 | /** 29 | * Get the notification's delivery channels. 30 | * 31 | * @return array 32 | */ 33 | public function via(object $notifiable): array 34 | { 35 | return ['mail', 'database', 'telegram']; 36 | } 37 | 38 | /** 39 | * Get the mail representation of the notification. 40 | */ 41 | public function toMail(object $notifiable): MailMessage 42 | { 43 | return (new MailMessage) 44 | ->subject('Sale Deleted - #' . $this->sale['invoice_number']) 45 | ->greeting('A sale has been deleted.') 46 | ->line('#' . $this->sale['invoice_number'].' By '.Auth::user()->name) 47 | ->line('Amount: ' . $this->sale['total_amount']) 48 | ->line('Deleted at: ' . \Carbon\Carbon::parse($this->sale['deleted_at'])->format('Y-m-d h:i A')); 49 | } 50 | 51 | public function toDatabase($notifiable) 52 | { 53 | return [ 54 | 'sale_id' => $this->sale['id'], 55 | 'amount' => $this->sale['total_amount'], 56 | 'message' => 'A sale has been deleted.', 57 | 'url' => url('/sales'), 58 | ]; 59 | } 60 | 61 | public function toTelegram($notifiable) 62 | { 63 | // Create the Telegram message 64 | return TelegramMessage::create() 65 | ->content( 66 | "A sale has been deleted.\n" . 67 | "Invoice Number: #" . $this->sale['invoice_number'] . "\n" . 68 | "Deleted By: " . Auth::user()->name . "\n" . 69 | "Amount: " . $this->sale['total_amount'] . "\n" . 70 | "Deleted at: " . \Carbon\Carbon::parse($this->sale['deleted_at'])->format('Y-m-d h:i A') . "\n" 71 | ) 72 | ->token($this->botToken); 73 | } 74 | 75 | /** 76 | * Get the array representation of the notification. 77 | * 78 | * @return array 79 | */ 80 | public function toArray(object $notifiable): array 81 | { 82 | return [ 83 | // 84 | ]; 85 | } 86 | } 87 | -------------------------------------------------------------------------------- /app/Providers/AppServiceProvider.php: -------------------------------------------------------------------------------- 1 | hasRole('super-admin'); 35 | }); 36 | 37 | // It's possible that the database hasn't been migrated yet, so we need to check that the setting exists before trying to retrieve it. 38 | if (!App::runningInConsole()) { 39 | $mailSetting = Setting::where('meta_key', 'mail_settings')->first(); 40 | } else { 41 | $mailSetting = null; 42 | } 43 | if ($mailSetting) { 44 | $mailSettings = json_decode($mailSetting->meta_value); 45 | Config::set(['mail.driver' => 'smtp']); 46 | Config::set(['mail.host' => $mailSettings->mail_host]); 47 | Config::set(['mail.port' => $mailSettings->mail_port]); 48 | Config::set(['mail.username' => $mailSettings->mail_username]); 49 | Config::set(['mail.password' => $mailSettings->mail_password]); 50 | Config::set(['mail.encryption' => $mailSettings->mail_encryption]); 51 | Config::set(['mail.from.address' => $mailSettings->mail_from_address]); 52 | Config::set(['mail.from.name' => $mailSettings->mail_from_name]); 53 | } 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /app/Traits/Userstamps.php: -------------------------------------------------------------------------------- 1 | created_by = Auth::id(); // Automatically set created_by to the authenticated user's ID 13 | }); 14 | } 15 | } -------------------------------------------------------------------------------- /artisan: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env php 2 | handleCommand(new ArgvInput); 14 | 15 | exit($status); 16 | -------------------------------------------------------------------------------- /bootstrap/app.php: -------------------------------------------------------------------------------- 1 | withRouting( 9 | web: __DIR__.'/../routes/web.php', 10 | commands: __DIR__.'/../routes/console.php', 11 | health: '/up', 12 | ) 13 | ->withMiddleware(function (Middleware $middleware) { 14 | $middleware->web(append: [ 15 | \App\Http\Middleware\HandleInertiaRequests::class, 16 | \Illuminate\Http\Middleware\AddLinkHeadersForPreloadedAssets::class, 17 | ]); 18 | 19 | // 20 | }) 21 | ->withExceptions(function (Exceptions $exceptions) { 22 | // 23 | })->create(); 24 | -------------------------------------------------------------------------------- /bootstrap/cache/.gitignore: -------------------------------------------------------------------------------- 1 | * 2 | !.gitignore 3 | -------------------------------------------------------------------------------- /bootstrap/providers.php: -------------------------------------------------------------------------------- 1 | 'authentication_log', 7 | 8 | // The database connection where the authentication_log table resides. Leave empty to use the default 9 | 'db_connection' => null, 10 | 11 | // The events the package listens for to log 12 | 'events' => [ 13 | 'login' => \Illuminate\Auth\Events\Login::class, 14 | 'failed' => \Illuminate\Auth\Events\Failed::class, 15 | 'logout' => \Illuminate\Auth\Events\Logout::class, 16 | 'logout-other-devices' => \Illuminate\Auth\Events\OtherDeviceLogout::class, 17 | ], 18 | 19 | 'listeners' => [ 20 | 'login' => \Rappasoft\LaravelAuthenticationLog\Listeners\LoginListener::class, 21 | 'failed' => \Rappasoft\LaravelAuthenticationLog\Listeners\FailedLoginListener::class, 22 | 'logout' => \Rappasoft\LaravelAuthenticationLog\Listeners\LogoutListener::class, 23 | 'logout-other-devices' => \Rappasoft\LaravelAuthenticationLog\Listeners\OtherDeviceLogoutListener::class, 24 | ], 25 | 26 | 'notifications' => [ 27 | 'new-device' => [ 28 | // Send the NewDevice notification 29 | 'enabled' => env('NEW_DEVICE_NOTIFICATION', true), 30 | 31 | // Use torann/geoip to attempt to get a location 32 | 'location' => false, 33 | 34 | // The Notification class to send 35 | 'template' => \Rappasoft\LaravelAuthenticationLog\Notifications\NewDevice::class, 36 | ], 37 | 'failed-login' => [ 38 | // Send the FailedLogin notification 39 | 'enabled' => env('FAILED_LOGIN_NOTIFICATION', false), 40 | 41 | // Use torann/geoip to attempt to get a location 42 | 'location' => false, 43 | 44 | // The Notification class to send 45 | 'template' => \Rappasoft\LaravelAuthenticationLog\Notifications\FailedLogin::class, 46 | ], 47 | ], 48 | 49 | // When the clean-up command is run, delete old logs greater than `purge` days 50 | // Don't schedule the clean-up command if you want to keep logs forever. 51 | 'purge' => 200, 52 | 53 | // If you are behind an CDN proxy, set 'behind_cdn.http_header_field' to the corresponding http header field of your cdn 54 | // For cloudflare you can have look at: https://developers.cloudflare.com/fundamentals/get-started/reference/http-request-headers/ 55 | // 'behind_cdn' => [ 56 | // 'http_header_field' => 'HTTP_CF_CONNECTING_IP' // used by Cloudflare 57 | // ], 58 | 59 | // If you are not a cdn user, use false 60 | 'behind_cdn' => false, 61 | ]; 62 | -------------------------------------------------------------------------------- /config/filesystems.php: -------------------------------------------------------------------------------- 1 | env('FILESYSTEM_DISK', 'local'), 17 | 18 | /* 19 | |-------------------------------------------------------------------------- 20 | | Filesystem Disks 21 | |-------------------------------------------------------------------------- 22 | | 23 | | Below you may configure as many filesystem disks as necessary, and you 24 | | may even configure multiple disks for the same driver. Examples for 25 | | most supported storage drivers are configured here for reference. 26 | | 27 | | Supported drivers: "local", "ftp", "sftp", "s3" 28 | | 29 | */ 30 | 31 | 'disks' => [ 32 | 33 | 'local' => [ 34 | 'driver' => 'local', 35 | 'root' => storage_path('app/private'), 36 | 'serve' => true, 37 | 'throw' => false, 38 | ], 39 | 40 | 'public' => [ 41 | 'driver' => 'local', 42 | 'root' => storage_path('app/public'), 43 | 'url' => env('APP_URL').'/storage', 44 | 'visibility' => 'public', 45 | 'throw' => false, 46 | ], 47 | 48 | 's3' => [ 49 | 'driver' => 's3', 50 | 'key' => env('AWS_ACCESS_KEY_ID'), 51 | 'secret' => env('AWS_SECRET_ACCESS_KEY'), 52 | 'region' => env('AWS_DEFAULT_REGION'), 53 | 'bucket' => env('AWS_BUCKET'), 54 | 'url' => env('AWS_URL'), 55 | 'endpoint' => env('AWS_ENDPOINT'), 56 | 'use_path_style_endpoint' => env('AWS_USE_PATH_STYLE_ENDPOINT', false), 57 | 'throw' => false, 58 | ], 59 | 60 | ], 61 | 62 | /* 63 | |-------------------------------------------------------------------------- 64 | | Symbolic Links 65 | |-------------------------------------------------------------------------- 66 | | 67 | | Here you may configure the symbolic links that will be created when the 68 | | `storage:link` Artisan command is executed. The array keys should be 69 | | the locations of the links and the values should be their targets. 70 | | 71 | */ 72 | 73 | 'links' => [ 74 | public_path('storage') => storage_path('app/public'), 75 | ], 76 | 77 | ]; 78 | -------------------------------------------------------------------------------- /config/services.php: -------------------------------------------------------------------------------- 1 | [ 18 | 'token' => env('POSTMARK_TOKEN'), 19 | ], 20 | 21 | 'ses' => [ 22 | 'key' => env('AWS_ACCESS_KEY_ID'), 23 | 'secret' => env('AWS_SECRET_ACCESS_KEY'), 24 | 'region' => env('AWS_DEFAULT_REGION', 'us-east-1'), 25 | ], 26 | 27 | 'resend' => [ 28 | 'key' => env('RESEND_KEY'), 29 | ], 30 | 31 | 'slack' => [ 32 | 'notifications' => [ 33 | 'bot_user_oauth_token' => env('SLACK_BOT_USER_OAUTH_TOKEN'), 34 | 'channel' => env('SLACK_BOT_USER_DEFAULT_CHANNEL'), 35 | ], 36 | ], 37 | 38 | ]; 39 | -------------------------------------------------------------------------------- /config/version.php: -------------------------------------------------------------------------------- 1 | '1.4.6', 5 | ]; -------------------------------------------------------------------------------- /database.sql: -------------------------------------------------------------------------------- 1 | ALTER TABLE `sales` 2 | ADD COLUMN `deleted_by` BIGINT UNSIGNED NULL AFTER `created_by`, 3 | ADD CONSTRAINT `fk_sales_deleted_by_users_id` FOREIGN KEY (`deleted_by`) REFERENCES `users`(`id`) ON DELETE SET NULL; 4 | -------------------------------------------------------------------------------- /database/.gitignore: -------------------------------------------------------------------------------- 1 | *.sqlite* 2 | -------------------------------------------------------------------------------- /database/factories/UserFactory.php: -------------------------------------------------------------------------------- 1 | 11 | */ 12 | class UserFactory extends Factory 13 | { 14 | /** 15 | * The current password being used by the factory. 16 | */ 17 | protected static ?string $password; 18 | 19 | /** 20 | * Define the model's default state. 21 | * 22 | * @return array 23 | */ 24 | public function definition(): array 25 | { 26 | return [ 27 | 'name' => fake()->name(), 28 | 'email' => fake()->unique()->safeEmail(), 29 | 'email_verified_at' => now(), 30 | 'password' => static::$password ??= Hash::make('password'), 31 | 'remember_token' => Str::random(10), 32 | ]; 33 | } 34 | 35 | /** 36 | * Indicate that the model's email address should be unverified. 37 | */ 38 | public function unverified(): static 39 | { 40 | return $this->state(fn (array $attributes) => [ 41 | 'email_verified_at' => null, 42 | ]); 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /database/migrations/2025_03_02_201045_add_deleted_by_to_sales_table.php: -------------------------------------------------------------------------------- 1 | unsignedBigInteger('deleted_by')->nullable()->after('created_by'); 16 | $table->foreign('deleted_by')->references('id')->on('users')->onDelete('set null'); 17 | }); 18 | } 19 | 20 | /** 21 | * Reverse the migrations. 22 | */ 23 | public function down(): void 24 | { 25 | Schema::table('sales', function (Blueprint $table) { 26 | $table->dropForeign(['deleted_by']); 27 | $table->dropColumn('deleted_by'); 28 | }); 29 | } 30 | }; 31 | -------------------------------------------------------------------------------- /database/seeders/ContactSeeder.php: -------------------------------------------------------------------------------- 1 | 'Guest', // Name of the walk-in customer 20 | 'email' => null, // Email is null for walk-in customers 21 | 'phone' => null, // Phone is null for walk-in customers 22 | 'address' => null, // Address is null for walk-in customers 23 | 'balance' => 0.00, // Default balance 24 | 'loyalty_points' => null, // No loyalty points for walk-in customers 25 | 'type' => 'customer', // Type set to 'guest' 26 | ]); 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /database/seeders/SettingSeeder.php: -------------------------------------------------------------------------------- 1 | 'shop_name', 'meta_value' => 'Info Shop'], 18 | ['meta_key' => 'shop_logo', 'meta_value' => 'oneshop-logo.png'], 19 | ['meta_key' => 'sale_receipt_note', 'meta_value' => 'Thank you'], 20 | ['meta_key' => 'sale_print_padding_right', 'meta_value' => '35'], 21 | ['meta_key' => 'sale_print_padding_left', 'meta_value' => '2'], 22 | ['meta_key' => 'sale_print_font', 'meta_value' => 'Arial, sans-serif'], 23 | ['meta_key' => 'show_barcode_store', 'meta_value' => 'on'], 24 | ['meta_key' => 'show_barcode_product_price', 'meta_value' => 'on'], 25 | ['meta_key' => 'show_barcode_product_name', 'meta_value' => 'on'], 26 | ['meta_key' => 'product_code_increment', 'meta_value' => '1000'], 27 | ['meta_key' => 'modules', 'meta_value' => 'Cheques'], 28 | ['meta_key' => 'misc_settings', 'meta_value' => json_encode([ 29 | 'optimize_image_size' => '0.5', 30 | 'optimize_image_width' => '400', 31 | 'cheque_alert' => '2', 32 | 'product_alert' => '1', 33 | ])], 34 | ]; 35 | 36 | Setting::insert($settings); 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /git commands to create archive.txt: -------------------------------------------------------------------------------- 1 | #get staged files and archive it, then run the compress command to include the build folder 2 | git archive -o update.zip HEAD $(git diff --name-only --cached) 3 | powershell -Command "Compress-Archive -Path 'public/build' -Update update.zip" 4 | 5 | #get staged and previously committed files 6 | git archive -o update.zip HEAD $(git diff --name-only HEAD^) 7 | 8 | #get the update files from the committed files 9 | git archive -o update.zip HEAD $(git diff --name-only abc123^ abc123) 10 | 11 | -------------------------------------------------------------------------------- /jsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "baseUrl": ".", 4 | "paths": { 5 | "@/*": ["resources/js/*"], 6 | "ziggy-js": ["./vendor/tightenco/ziggy"] 7 | } 8 | }, 9 | "exclude": ["node_modules", "public"] 10 | } 11 | -------------------------------------------------------------------------------- /lang/vendor/authentication-log/en.json: -------------------------------------------------------------------------------- 1 | { 2 | "A failed login to your account": "A failed login to your account", 3 | "Account:": "Account:", 4 | "Browser:": "Browser:", 5 | "Hello!": "Hello!", 6 | "If this was you, you can ignore this alert. If you suspect any suspicious activity on your account, please change your password.": "If this was you, you can ignore this alert. If you suspect any suspicious activity on your account, please change your password.", 7 | "IP Address:": "IP Address:", 8 | "Location:": "Location:", 9 | "Regards,": "Regards,", 10 | "There has been a failed login attempt to your :app account.": "There has been a failed login attempt to your :app account.", 11 | "Time:": "Time:", 12 | "Unknown City": "Unknown City", 13 | "Unknown State": "Unknown State", 14 | "Your :app account logged in from a new device.": "Your :app account logged in from a new device." 15 | } 16 | -------------------------------------------------------------------------------- /lang/vendor/authentication-log/fr.json: -------------------------------------------------------------------------------- 1 | { 2 | "A failed login to your account": "Échec de la connexion à votre compte", 3 | "Account:": "Compte :", 4 | "Browser:": "Navigateur :", 5 | "Hello!": "Bonjour,", 6 | "If this was you, you can ignore this alert. If you suspect any suspicious activity on your account, please change your password.": "Si c’était vous, vous pouvez ignorer cette alerte. Si vous soupçonnez une activité suspecte sur votre compte, veuillez modifier votre mot de passe.", 7 | "IP Address:": "Adresse IP :", 8 | "Location:": "Emplacement :", 9 | "Regards,": "Cordialement,", 10 | "There has been a failed login attempt to your :app account.": "Une tentative de connexion à votre compte sur :app a échoué.", 11 | "Time:": "Heure :", 12 | "Unknown City": "Ville inconnue", 13 | "Unknown State": "État inconnu", 14 | "Your :app account logged in from a new device.": "Connexion à votre compte sur :app depuis un nouvel appareil." 15 | } 16 | -------------------------------------------------------------------------------- /lang/vendor/authentication-log/pt_BR.json: -------------------------------------------------------------------------------- 1 | { 2 | "A failed login to your account": "Um login com falha em sua conta", 3 | "Account:": "Conta", 4 | "Browser:": "Navegador:", 5 | "If this was you, you can ignore this alert. If you suspect any suspicious activity on your account, please change your password.": "Se foi você, pode ignorar este alerta. Se você suspeitar de qualquer atividade suspeita em sua conta, altere sua senha.", 6 | "IP Address:": "Endereço de IP:", 7 | "Location:": "Localização:", 8 | "Regards,": "Obrigado,", 9 | "There has been a failed login attempt to your :app account.": "Houve uma falha na tentativa de login em seu :app conta.", 10 | "Time:": "Data:", 11 | "Unknown City": "Cidade Desconhecida", 12 | "Unknown State": "Estado Desconhecido", 13 | "Your :app account logged in from a new device.": "Sua :app foi acessada em um novo dispositivo" 14 | } 15 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "author": "Nifras", 3 | "license": "MIT", 4 | "private": true, 5 | "type": "module", 6 | "scripts": { 7 | "dev": "vite", 8 | "build": "vite build" 9 | }, 10 | "devDependencies": { 11 | "@headlessui/react": "^2.0.0", 12 | "@inertiajs/react": "^2.0.3", 13 | "@tailwindcss/forms": "^0.5.3", 14 | "@vitejs/plugin-react": "^4.3.4", 15 | "autoprefixer": "^10.4.12", 16 | "axios": "^1.7.9", 17 | "laravel-vite-plugin": "^1.2.0", 18 | "postcss": "^8.5.2", 19 | "react": "^19.0.0", 20 | "react-dom": "^19.0.0", 21 | "tailwindcss": "^3.4.17", 22 | "vite": "^6.1.0" 23 | }, 24 | "dependencies": { 25 | "@emotion/react": "^11.14.0", 26 | "@emotion/styled": "^11.13.0", 27 | "@fortawesome/fontawesome-svg-core": "^6.7.2", 28 | "@fortawesome/free-regular-svg-icons": "^6.7.2", 29 | "@fortawesome/free-solid-svg-icons": "^6.7.2", 30 | "@fortawesome/react-fontawesome": "^0.2.2", 31 | "@inertiajs/progress": "^0.2.7", 32 | "@mui/icons-material": "^6.4.4", 33 | "@mui/material": "^6.4.4", 34 | "@mui/x-charts": "^7.26.0", 35 | "@mui/x-data-grid": "^7.26.0", 36 | "@mui/x-date-pickers": "^7.18.0", 37 | "alpinejs": "^3.14.8", 38 | "browser-image-compression": "^2.0.2", 39 | "dayjs": "^1.11.13", 40 | "ejs": "^3.1.10", 41 | "jsbarcode": "^3.11.6", 42 | "lodash": "^4.17.21", 43 | "mustache": "^4.2.0", 44 | "nanoid": "^5.0.9", 45 | "notistack": "^3.0.1", 46 | "nprogress": "^0.2.0", 47 | "numeral": "^2.0.6", 48 | "react-data-table-component": "^7.6.2", 49 | "react-dropzone": "^14.3.8", 50 | "react-hot-keys": "^2.7.3", 51 | "react-select": "^5.10.0", 52 | "react-to-print": "^3.0.4", 53 | "sweetalert2": "^11.14.5" 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /phpunit.xml: -------------------------------------------------------------------------------- 1 | 2 | 7 | 8 | 9 | tests/Unit 10 | 11 | 12 | tests/Feature 13 | 14 | 15 | 16 | 17 | app 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | -------------------------------------------------------------------------------- /postcss.config.js: -------------------------------------------------------------------------------- 1 | export default { 2 | plugins: { 3 | tailwindcss: {}, 4 | autoprefixer: {}, 5 | }, 6 | }; 7 | -------------------------------------------------------------------------------- /public/.htaccess: -------------------------------------------------------------------------------- 1 | 2 | 3 | Options -MultiViews -Indexes 4 | 5 | 6 | RewriteEngine On 7 | 8 | # Handle Authorization Header 9 | RewriteCond %{HTTP:Authorization} . 10 | RewriteRule .* - [E=HTTP_AUTHORIZATION:%{HTTP:Authorization}] 11 | 12 | # Redirect Trailing Slashes If Not A Folder... 13 | RewriteCond %{REQUEST_FILENAME} !-d 14 | RewriteCond %{REQUEST_URI} (.+)/$ 15 | RewriteRule ^ %1 [L,R=301] 16 | 17 | # Send Requests To Front Controller... 18 | RewriteCond %{REQUEST_FILENAME} !-d 19 | RewriteCond %{REQUEST_FILENAME} !-f 20 | RewriteRule ^ index.php [L] 21 | 22 | -------------------------------------------------------------------------------- /public/Infoshop-icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NifrasUsanar/InfoShop/229f52dbf7513ac71455111e334c18e9454dcd28/public/Infoshop-icon.png -------------------------------------------------------------------------------- /public/css/custom.css: -------------------------------------------------------------------------------- 1 | tr.receipt-details-row td{ 2 | padding-top: 0px; 3 | /* border-bottom-color: black; */ 4 | } 5 | 6 | tr.receipt-summary-row td{ 7 | color:black; 8 | } 9 | 10 | .receipt-container{ 11 | color:black; 12 | } 13 | 14 | .receipt-meta{ 15 | color: black; 16 | } -------------------------------------------------------------------------------- /public/index.php: -------------------------------------------------------------------------------- 1 | handleRequest(Request::capture()); 18 | -------------------------------------------------------------------------------- /public/oneshop-logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NifrasUsanar/InfoShop/229f52dbf7513ac71455111e334c18e9454dcd28/public/oneshop-logo.png -------------------------------------------------------------------------------- /public/robots.txt: -------------------------------------------------------------------------------- 1 | User-agent: * 2 | Disallow: 3 | -------------------------------------------------------------------------------- /public/tinymce/langs/README.md: -------------------------------------------------------------------------------- 1 | This is where language files should be placed. 2 | 3 | Please DO NOT translate these directly, use this service instead: https://crowdin.com/project/tinymce 4 | -------------------------------------------------------------------------------- /public/tinymce/license.md: -------------------------------------------------------------------------------- 1 | # Software License Agreement 2 | 3 | **TinyMCE** – [](https://github.com/tinymce/tinymce) 4 | Copyright (c) 2024, Ephox Corporation DBA Tiny Technologies, Inc. 5 | 6 | Licensed under the terms of [GNU General Public License Version 2 or later](http://www.gnu.org/licenses/gpl.html). 7 | -------------------------------------------------------------------------------- /public/tinymce/plugins/anchor/plugin.min.js: -------------------------------------------------------------------------------- 1 | /** 2 | * TinyMCE version 7.6.1 (2025-01-22) 3 | */ 4 | !function(){"use strict";var e=tinymce.util.Tools.resolve("tinymce.PluginManager"),t=tinymce.util.Tools.resolve("tinymce.dom.RangeUtils"),o=tinymce.util.Tools.resolve("tinymce.util.Tools");const n=("allow_html_in_named_anchor",e=>e.options.get("allow_html_in_named_anchor"));const a="a:not([href])",r=e=>!e,i=e=>e.getAttribute("id")||e.getAttribute("name")||"",l=e=>(e=>"a"===e.nodeName.toLowerCase())(e)&&!e.getAttribute("href")&&""!==i(e),s=e=>e.dom.getParent(e.selection.getStart(),a),d=(e,a)=>{const r=s(e);r?((e,t,o)=>{o.removeAttribute("name"),o.id=t,e.addVisual(),e.undoManager.add()})(e,a,r):((e,a)=>{e.undoManager.transact((()=>{n(e)||e.selection.collapse(!0),e.selection.isCollapsed()?e.insertContent(e.dom.createHTML("a",{id:a})):((e=>{const n=e.dom;t(n).walk(e.selection.getRng(),(e=>{o.each(e,(e=>{var t;l(t=e)&&!t.firstChild&&n.remove(e,!1)}))}))})(e),e.formatter.remove("namedAnchor",void 0,void 0,!0),e.formatter.apply("namedAnchor",{value:a}),e.addVisual())}))})(e,a),e.focus()},c=e=>(e=>r(e.attr("href"))&&!r(e.attr("id")||e.attr("name")))(e)&&!e.firstChild,m=e=>t=>{for(let o=0;ot=>{const o=()=>{t.setEnabled(e.selection.isEditable())};return e.on("NodeChange",o),o(),()=>{e.off("NodeChange",o)}};e.add("anchor",(e=>{(e=>{(0,e.options.register)("allow_html_in_named_anchor",{processor:"boolean",default:!1})})(e),(e=>{e.on("PreInit",(()=>{e.parser.addNodeFilter("a",m("false")),e.serializer.addNodeFilter("a",m(null))}))})(e),(e=>{e.addCommand("mceAnchor",(()=>{(e=>{const t=(e=>{const t=s(e);return t?i(t):""})(e);e.windowManager.open({title:"Anchor",size:"normal",body:{type:"panel",items:[{name:"id",type:"input",label:"ID",placeholder:"example"}]},buttons:[{type:"cancel",name:"cancel",text:"Cancel"},{type:"submit",name:"save",text:"Save",primary:!0}],initialData:{id:t},onSubmit:t=>{((e,t)=>/^[A-Za-z][A-Za-z0-9\-:._]*$/.test(t)?(d(e,t),!0):(e.windowManager.alert("ID should start with a letter, followed only by letters, numbers, dashes, dots, colons or underscores."),!1))(e,t.getData().id)&&t.close()}})})(e)}))})(e),(e=>{const t=()=>e.execCommand("mceAnchor");e.ui.registry.addToggleButton("anchor",{icon:"bookmark",tooltip:"Anchor",onAction:t,onSetup:t=>{const o=e.selection.selectorChangedWithUnbind("a:not([href])",t.setActive).unbind,n=u(e)(t);return()=>{o(),n()}}}),e.ui.registry.addMenuItem("anchor",{icon:"bookmark",text:"Anchor...",onAction:t,onSetup:u(e)})})(e),e.on("PreInit",(()=>{(e=>{e.formatter.register("namedAnchor",{inline:"a",selector:a,remove:"all",split:!0,deep:!0,attributes:{id:"%value"},onmatch:(e,t,o)=>l(e)})})(e)}))}))}(); -------------------------------------------------------------------------------- /public/tinymce/plugins/autoresize/plugin.min.js: -------------------------------------------------------------------------------- 1 | /** 2 | * TinyMCE version 7.6.1 (2025-01-22) 3 | */ 4 | !function(){"use strict";var e=tinymce.util.Tools.resolve("tinymce.PluginManager"),t=tinymce.util.Tools.resolve("tinymce.Env");const o=e=>t=>t.options.get(e),n=o("min_height"),s=o("max_height"),i=o("autoresize_overflow_padding"),r=o("autoresize_bottom_margin"),g=(e,t)=>{const o=e.getBody();o&&(o.style.overflowY=t?"":"hidden",t||(o.scrollTop=0))},l=(e,t,o,n)=>{var s;const i=parseInt(null!==(s=e.getStyle(t,o,n))&&void 0!==s?s:"",10);return isNaN(i)?0:i},a=(e,o,r,c)=>{var d;const u=e.dom,h=e.getDoc();if(!h)return;if((e=>e.plugins.fullscreen&&e.plugins.fullscreen.isFullscreen())(e))return void g(e,!0);const m=h.documentElement,f=c?c():i(e),p=null!==(d=n(e))&&void 0!==d?d:e.getElement().offsetHeight;let y=p;const S=l(u,m,"margin-top",!0),v=l(u,m,"margin-bottom",!0);let C=m.offsetHeight+S+v+f;C<0&&(C=0);const H=e.getContainer().offsetHeight-e.getContentAreaContainer().offsetHeight;C+H>p&&(y=C+H);const b=s(e);b&&y>b?(y=b,g(e,!0)):g(e,!1);const w=o.get();if(w.set&&(e.dom.setStyles(e.getDoc().documentElement,{"min-height":0}),e.dom.setStyles(e.getBody(),{"min-height":"inherit"})),y!==w.totalHeight&&(C-f!==w.contentHeight||!w.set)){const n=y-w.totalHeight;if(u.setStyle(e.getContainer(),"height",y+"px"),o.set({totalHeight:y,contentHeight:C,set:!0}),(e=>{e.dispatch("ResizeEditor")})(e),t.browser.isSafari()&&(t.os.isMacOS()||t.os.isiOS())){const t=e.getWin();t.scrollTo(t.pageXOffset,t.pageYOffset)}e.hasFocus()&&(e=>{if("setcontent"===(null==e?void 0:e.type.toLowerCase())){const t=e;return!0===t.selection||!0===t.paste}return!1})(r)&&e.selection.scrollIntoView(),(t.browser.isSafari()||t.browser.isChromium())&&n<0&&a(e,o,r,c)}};e.add("autoresize",(e=>{if((e=>{const t=e.options.register;t("autoresize_overflow_padding",{processor:"number",default:1}),t("autoresize_bottom_margin",{processor:"number",default:50})})(e),e.options.isSet("resize")||e.options.set("resize",!1),!e.inline){const o=(e=>{let t={totalHeight:0,contentHeight:0,set:!1};return{get:()=>t,set:e=>{t=e}}})();((e,t)=>{e.addCommand("mceAutoResize",(()=>{a(e,t)}))})(e,o),((e,o)=>{const n=()=>r(e);e.on("init",(s=>{const r=i(e),g=e.dom;g.setStyles(e.getDoc().documentElement,{height:"auto"}),t.browser.isEdge()||t.browser.isIE()?g.setStyles(e.getBody(),{paddingLeft:r,paddingRight:r,"min-height":0}):g.setStyles(e.getBody(),{paddingLeft:r,paddingRight:r}),a(e,o,s,n)})),e.on("NodeChange SetContent keyup FullscreenStateChanged ResizeContent",(t=>{a(e,o,t,n)}))})(e,o)}}))}(); -------------------------------------------------------------------------------- /public/tinymce/plugins/code/plugin.min.js: -------------------------------------------------------------------------------- 1 | /** 2 | * TinyMCE version 7.6.1 (2025-01-22) 3 | */ 4 | !function(){"use strict";tinymce.util.Tools.resolve("tinymce.PluginManager").add("code",(e=>((e=>{e.addCommand("mceCodeEditor",(()=>{(e=>{const o=(e=>e.getContent({source_view:!0}))(e);e.windowManager.open({title:"Source Code",size:"large",body:{type:"panel",items:[{type:"textarea",name:"code"}]},buttons:[{type:"cancel",name:"cancel",text:"Cancel"},{type:"submit",name:"save",text:"Save",primary:!0}],initialData:{code:o},onSubmit:o=>{((e,o)=>{e.focus(),e.undoManager.transact((()=>{e.setContent(o)})),e.selection.setCursorLocation(),e.nodeChanged()})(e,o.getData().code),o.close()}})})(e)}))})(e),(e=>{const o=()=>e.execCommand("mceCodeEditor");e.ui.registry.addButton("code",{icon:"sourcecode",tooltip:"Source code",onAction:o}),e.ui.registry.addMenuItem("code",{icon:"sourcecode",text:"Source code",onAction:o})})(e),{})))}(); -------------------------------------------------------------------------------- /public/tinymce/plugins/help/js/i18n/keynav/zh_CN.js: -------------------------------------------------------------------------------- 1 | tinymce.Resource.add('tinymce.html-i18n.help-keynav.zh_CN', 2 | '

开始键盘导航

\n' + 3 | '\n' + 4 | '
\n' + 5 | '
使菜单栏处于焦点
\n' + 6 | '
Windows 或 Linux:Alt+F9
\n' + 7 | '
macOS:⌥F9
\n' + 8 | '
使工具栏处于焦点
\n' + 9 | '
Windows 或 Linux:Alt+F10
\n' + 10 | '
macOS:⌥F10
\n' + 11 | '
使页脚处于焦点
\n' + 12 | '
Windows 或 Linux:Alt+F11
\n' + 13 | '
macOS:⌥F11
\n' + 14 | '
使通知处于焦点
\n' + 15 | '
Windows 或 Linux:Alt+F12
\n' + 16 | '
macOS:⌥F12
\n' + 17 | '
使上下文工具栏处于焦点
\n' + 18 | '
Windows、Linux 或 macOS:Ctrl+F9
\n' + 19 | '
\n' + 20 | '\n' + 21 | '

导航将在第一个 UI 项上开始,其中突出显示该项,或者对于页脚元素路径中的第一项,将为其添加下划线。

\n' + 22 | '\n' + 23 | '

在 UI 部分之间导航

\n' + 24 | '\n' + 25 | '

要从一个 UI 部分移至下一个,请按 Tab

\n' + 26 | '\n' + 27 | '

要从一个 UI 部分移至上一个,请按 Shift+Tab

\n' + 28 | '\n' + 29 | '

这些 UI 部分的 Tab 顺序为:

\n' + 30 | '\n' + 31 | '
    \n' + 32 | '
  1. 菜单栏
  2. \n' + 33 | '
  3. 每个工具栏组
  4. \n' + 34 | '
  5. 边栏
  6. \n' + 35 | '
  7. 页脚中的元素路径
  8. \n' + 36 | '
  9. 页脚中的字数切换按钮
  10. \n' + 37 | '
  11. 页脚中的品牌链接
  12. \n' + 38 | '
  13. 页脚中的编辑器调整大小图柄
  14. \n' + 39 | '
\n' + 40 | '\n' + 41 | '

如果不存在某个 UI 部分,则跳过它。

\n' + 42 | '\n' + 43 | '

如果键盘导航焦点在页脚,并且没有可见的边栏,则按 Shift+Tab 将焦点移至第一个工具栏组而非最后一个。

\n' + 44 | '\n' + 45 | '

在 UI 部分内导航

\n' + 46 | '\n' + 47 | '

要从一个 UI 元素移至下一个,请按相应的箭头键。

\n' + 48 | '\n' + 49 | '

箭头键

\n' + 50 | '\n' + 51 | '
    \n' + 52 | '
  • 在菜单栏中的菜单之间移动。
  • \n' + 53 | '
  • 打开菜单中的子菜单。
  • \n' + 54 | '
  • 在工具栏组中的按钮之间移动。
  • \n' + 55 | '
  • 在页脚的元素路径中的各项之间移动。
  • \n' + 56 | '
\n' + 57 | '\n' + 58 | '

箭头键

\n' + 59 | '\n' + 60 | '
    \n' + 61 | '
  • 在菜单中的菜单项之间移动。
  • \n' + 62 | '
  • 在工具栏弹出菜单中的各项之间移动。
  • \n' + 63 | '
\n' + 64 | '\n' + 65 | '

箭头键在具有焦点的 UI 部分内循环。

\n' + 66 | '\n' + 67 | '

要关闭打开的菜单、打开的子菜单或打开的弹出菜单,请按 Esc 键。

\n' + 68 | '\n' + 69 | '

如果当前的焦点在特定 UI 部分的“顶部”,则按 Esc 键还将完全退出键盘导航。

\n' + 70 | '\n' + 71 | '

执行菜单项或工具栏按钮

\n' + 72 | '\n' + 73 | '

当突出显示所需的菜单项或工具栏按钮时,按 ReturnEnter空格以执行该项。

\n' + 74 | '\n' + 75 | '

在非标签页式对话框中导航

\n' + 76 | '\n' + 77 | '

在非标签页式对话框中,当对话框打开时,第一个交互组件获得焦点。

\n' + 78 | '\n' + 79 | '

通过按 TabShift+Tab,在交互对话框组件之间导航。

\n' + 80 | '\n' + 81 | '

在标签页式对话框中导航

\n' + 82 | '\n' + 83 | '

在标签页式对话框中,当对话框打开时,标签页菜单中的第一个按钮获得焦点。

\n' + 84 | '\n' + 85 | '

通过按 TabShift+Tab,在此对话框的交互组件之间导航。

\n' + 86 | '\n' + 87 | '

通过将焦点移至另一对话框标签页的菜单,然后按相应的箭头键以在可用的标签页间循环,从而切换到该对话框标签页。

\n'); -------------------------------------------------------------------------------- /public/tinymce/plugins/help/js/i18n/keynav/zh_TW.js: -------------------------------------------------------------------------------- 1 | tinymce.Resource.add('tinymce.html-i18n.help-keynav.zh_TW', 2 | '

開始鍵盤瀏覽

\n' + 3 | '\n' + 4 | '
\n' + 5 | '
跳至功能表列
\n' + 6 | '
Windows 或 Linux:Alt+F9
\n' + 7 | '
macOS:⌥F9
\n' + 8 | '
跳至工具列
\n' + 9 | '
Windows 或 Linux:Alt+F10
\n' + 10 | '
macOS:⌥F10
\n' + 11 | '
跳至頁尾
\n' + 12 | '
Windows 或 Linux:Alt+F11
\n' + 13 | '
macOS:⌥F11
\n' + 14 | '
跳至通知
\n' + 15 | '
Windows 或 Linux:Alt+F12
\n' + 16 | '
macOS:⌥F12
\n' + 17 | '
跳至關聯式工具列
\n' + 18 | '
Windows、Linux 或 macOS:Ctrl+F9
\n' + 19 | '
\n' + 20 | '\n' + 21 | '

瀏覽會從第一個 UI 項目開始,該項目會反白顯示,但如果是「頁尾」元素路徑的第一項,\n' + 22 | ' 則加底線。

\n' + 23 | '\n' + 24 | '

在 UI 區段之間瀏覽

\n' + 25 | '\n' + 26 | '

從 UI 區段移至下一個,請按 Tab

\n' + 27 | '\n' + 28 | '

從 UI 區段移回上一個,請按 Shift+Tab

\n' + 29 | '\n' + 30 | '

這些 UI 區段的 Tab 順序如下:

\n' + 31 | '\n' + 32 | '
    \n' + 33 | '
  1. 功能表列
  2. \n' + 34 | '
  3. 各個工具列群組
  4. \n' + 35 | '
  5. 側邊欄
  6. \n' + 36 | '
  7. 頁尾中的元素路徑
  8. \n' + 37 | '
  9. 頁尾中字數切換按鈕
  10. \n' + 38 | '
  11. 頁尾中的品牌連結
  12. \n' + 39 | '
  13. 頁尾中編輯器調整大小控點
  14. \n' + 40 | '
\n' + 41 | '\n' + 42 | '

如果 UI 區段未顯示,表示已略過該區段。

\n' + 43 | '\n' + 44 | '

如果鍵盤瀏覽跳至頁尾,但沒有顯示側邊欄,則按下 Shift+Tab\n' + 45 | ' 會跳至第一個工具列群組,而不是最後一個。

\n' + 46 | '\n' + 47 | '

在 UI 區段之內瀏覽

\n' + 48 | '\n' + 49 | '

在兩個 UI 元素之間移動,請按適當的方向鍵。

\n' + 50 | '\n' + 51 | '

向左向右方向鍵

\n' + 52 | '\n' + 53 | '
    \n' + 54 | '
  • 在功能表列中的功能表之間移動。
  • \n' + 55 | '
  • 開啟功能表中的子功能表。
  • \n' + 56 | '
  • 在工具列群組中的按鈕之間移動。
  • \n' + 57 | '
  • 在頁尾的元素路徑中項目之間移動。
  • \n' + 58 | '
\n' + 59 | '\n' + 60 | '

向下向上方向鍵

\n' + 61 | '\n' + 62 | '
    \n' + 63 | '
  • 在功能表中的功能表項目之間移動。
  • \n' + 64 | '
  • 在工具列快顯功能表中的項目之間移動。
  • \n' + 65 | '
\n' + 66 | '\n' + 67 | '

方向鍵會在所跳至 UI 區段之內循環。

\n' + 68 | '\n' + 69 | '

若要關閉已開啟的功能表、已開啟的子功能表,或已開啟的快顯功能表,請按 Esc 鍵。

\n' + 70 | '\n' + 71 | '

如果目前已跳至特定 UI 區段的「頂端」,則按 Esc 鍵也會結束\n' + 72 | ' 整個鍵盤瀏覽。

\n' + 73 | '\n' + 74 | '

執行功能表列項目或工具列按鈕

\n' + 75 | '\n' + 76 | '

當想要的功能表項目或工具列按鈕已反白顯示時,按 ReturnEnter、\n' + 77 | ' 或空白鍵即可執行該項目。

\n' + 78 | '\n' + 79 | '

瀏覽非索引標籤式對話方塊

\n' + 80 | '\n' + 81 | '

在非索引標籤式對話方塊中,開啟對話方塊時會跳至第一個互動元件。

\n' + 82 | '\n' + 83 | '

TabShift+Tab 即可在互動式對話方塊元件之間瀏覽。

\n' + 84 | '\n' + 85 | '

瀏覽索引標籤式對話方塊

\n' + 86 | '\n' + 87 | '

在索引標籤式對話方塊中,開啟對話方塊時會跳至索引標籤式功能表中的第一個按鈕。

\n' + 88 | '\n' + 89 | '

若要在此對話方塊的互動式元件之間瀏覽,請按 Tab 或\n' + 90 | ' Shift+Tab

\n' + 91 | '\n' + 92 | '

先跳至索引標籤式功能表,然後按適當的方向鍵,即可切換至另一個對話方塊索引標籤,\n' + 93 | ' 以循環瀏覽可用的索引標籤。

\n'); -------------------------------------------------------------------------------- /public/tinymce/plugins/insertdatetime/plugin.min.js: -------------------------------------------------------------------------------- 1 | /** 2 | * TinyMCE version 7.6.1 (2025-01-22) 3 | */ 4 | !function(){"use strict";var e=tinymce.util.Tools.resolve("tinymce.PluginManager");const t=e=>t=>t.options.get(e),a=t("insertdatetime_dateformat"),n=t("insertdatetime_timeformat"),r=t("insertdatetime_formats"),s=t("insertdatetime_element"),i="Sun Mon Tue Wed Thu Fri Sat Sun".split(" "),o="Sunday Monday Tuesday Wednesday Thursday Friday Saturday Sunday".split(" "),l="Jan Feb Mar Apr May Jun Jul Aug Sep Oct Nov Dec".split(" "),m="January February March April May June July August September October November December".split(" "),c=(e,t)=>{if((e=""+e).length(t=(t=(t=(t=(t=(t=(t=(t=(t=(t=(t=(t=(t=(t=(t=t.replace("%D","%m/%d/%Y")).replace("%r","%I:%M:%S %p")).replace("%Y",""+a.getFullYear())).replace("%y",""+a.getYear())).replace("%m",c(a.getMonth()+1,2))).replace("%d",c(a.getDate(),2))).replace("%H",""+c(a.getHours(),2))).replace("%M",""+c(a.getMinutes(),2))).replace("%S",""+c(a.getSeconds(),2))).replace("%I",""+((a.getHours()+11)%12+1))).replace("%p",a.getHours()<12?"AM":"PM")).replace("%B",""+e.translate(m[a.getMonth()]))).replace("%b",""+e.translate(l[a.getMonth()]))).replace("%A",""+e.translate(o[a.getDay()]))).replace("%a",""+e.translate(i[a.getDay()]))).replace("%%","%"),u=(e,t)=>{if(s(e)&&e.selection.isEditable()){const a=d(e,t);let n;n=/%[HMSIp]/.test(t)?d(e,"%Y-%m-%dT%H:%M"):d(e,"%Y-%m-%d");const r=e.dom.getParent(e.selection.getStart(),"time");r?((e,t,a,n)=>{const r=e.dom.create("time",{datetime:a},n);e.dom.replace(r,t),e.selection.select(r,!0),e.selection.collapse(!1)})(e,r,n,a):e.insertContent('")}else e.insertContent(d(e,t))};var p=tinymce.util.Tools.resolve("tinymce.util.Tools");const g=e=>t=>{const a=()=>{t.setEnabled(e.selection.isEditable())};return e.on("NodeChange",a),a(),()=>{e.off("NodeChange",a)}};e.add("insertdatetime",(e=>{(e=>{const t=e.options.register;t("insertdatetime_dateformat",{processor:"string",default:e.translate("%Y-%m-%d")}),t("insertdatetime_timeformat",{processor:"string",default:e.translate("%H:%M:%S")}),t("insertdatetime_formats",{processor:"string[]",default:["%H:%M:%S","%Y-%m-%d","%I:%M:%S %p","%D"]}),t("insertdatetime_element",{processor:"boolean",default:!1})})(e),(e=>{e.addCommand("mceInsertDate",((t,n)=>{u(e,null!=n?n:a(e))})),e.addCommand("mceInsertTime",((t,a)=>{u(e,null!=a?a:n(e))}))})(e),(e=>{const t=r(e),a=(e=>{let t=e;return{get:()=>t,set:e=>{t=e}}})((e=>{const t=r(e);return t.length>0?t[0]:n(e)})(e)),s=t=>e.execCommand("mceInsertDate",!1,t);e.ui.registry.addSplitButton("insertdatetime",{icon:"insert-time",tooltip:"Insert date/time",select:e=>e===a.get(),fetch:a=>{a(p.map(t,(t=>({type:"choiceitem",text:d(e,t),value:t}))))},onAction:e=>{s(a.get())},onItemAction:(e,t)=>{a.set(t),s(t)},onSetup:g(e)});const i=e=>()=>{a.set(e),s(e)};e.ui.registry.addNestedMenuItem("insertdatetime",{icon:"insert-time",text:"Date/time",getSubmenuItems:()=>p.map(t,(t=>({type:"menuitem",text:d(e,t),onAction:i(t)}))),onSetup:g(e)})})(e)}))}(); -------------------------------------------------------------------------------- /public/tinymce/plugins/nonbreaking/plugin.min.js: -------------------------------------------------------------------------------- 1 | /** 2 | * TinyMCE version 7.6.1 (2025-01-22) 3 | */ 4 | !function(){"use strict";var n=tinymce.util.Tools.resolve("tinymce.PluginManager");const e=n=>e=>typeof e===n,o=e("boolean"),a=e("number"),t=n=>e=>e.options.get(n),i=t("nonbreaking_force_tab"),s=t("nonbreaking_wrap"),r=(n,e)=>{let o="";for(let a=0;a{const o=s(n)||n.plugins.visualchars?`${r(" ",e)}`:r(" ",e);n.undoManager.transact((()=>n.insertContent(o)))};var l=tinymce.util.Tools.resolve("tinymce.util.VK");const u=n=>e=>{const o=()=>{e.setEnabled(n.selection.isEditable())};return n.on("NodeChange",o),o(),()=>{n.off("NodeChange",o)}};n.add("nonbreaking",(n=>{(n=>{const e=n.options.register;e("nonbreaking_force_tab",{processor:n=>o(n)?{value:n?3:0,valid:!0}:a(n)?{value:n,valid:!0}:{valid:!1,message:"Must be a boolean or number."},default:!1}),e("nonbreaking_wrap",{processor:"boolean",default:!0})})(n),(n=>{n.addCommand("mceNonBreaking",(()=>{c(n,1)}))})(n),(n=>{const e=()=>n.execCommand("mceNonBreaking");n.ui.registry.addButton("nonbreaking",{icon:"non-breaking",tooltip:"Nonbreaking space",onAction:e,onSetup:u(n)}),n.ui.registry.addMenuItem("nonbreaking",{icon:"non-breaking",text:"Nonbreaking space",onAction:e,onSetup:u(n)})})(n),(n=>{const e=i(n);e>0&&n.on("keydown",(o=>{if(o.keyCode===l.TAB&&!o.isDefaultPrevented()){if(o.shiftKey)return;o.preventDefault(),o.stopImmediatePropagation(),c(n,e)}}))})(n)}))}(); -------------------------------------------------------------------------------- /public/tinymce/plugins/pagebreak/plugin.min.js: -------------------------------------------------------------------------------- 1 | /** 2 | * TinyMCE version 7.6.1 (2025-01-22) 3 | */ 4 | !function(){"use strict";var e=tinymce.util.Tools.resolve("tinymce.PluginManager"),a=tinymce.util.Tools.resolve("tinymce.Env");const t=e=>a=>a.options.get(e),n=t("pagebreak_separator"),o=t("pagebreak_split_block"),r="mce-pagebreak",s=e=>{const t=``;return e?`

${t}

`:t},c=e=>a=>{const t=()=>{a.setEnabled(e.selection.isEditable())};return e.on("NodeChange",t),t(),()=>{e.off("NodeChange",t)}};e.add("pagebreak",(e=>{(e=>{const a=e.options.register;a("pagebreak_separator",{processor:"string",default:"\x3c!-- pagebreak --\x3e"}),a("pagebreak_split_block",{processor:"boolean",default:!1})})(e),(e=>{e.addCommand("mcePageBreak",(()=>{e.insertContent(s(o(e)))}))})(e),(e=>{const a=()=>e.execCommand("mcePageBreak");e.ui.registry.addButton("pagebreak",{icon:"page-break",tooltip:"Page break",onAction:a,onSetup:c(e)}),e.ui.registry.addMenuItem("pagebreak",{text:"Page break",icon:"page-break",onAction:a,onSetup:c(e)})})(e),(e=>{const a=n(e),t=()=>o(e),c=new RegExp(a.replace(/[\?\.\*\[\]\(\)\{\}\+\^\$\:]/g,(e=>"\\"+e)),"gi");e.on("BeforeSetContent",(e=>{e.content=e.content.replace(c,s(t()))})),e.on("PreInit",(()=>{e.serializer.addNodeFilter("img",(n=>{let o,s,c=n.length;for(;c--;)if(o=n[c],s=o.attr("class"),s&&-1!==s.indexOf(r)){const n=o.parent;if(n&&e.schema.getBlockElements()[n.name]&&t()){n.type=3,n.value=a,n.raw=!0,o.remove();continue}o.type=3,o.value=a,o.raw=!0}}))}))})(e),(e=>{e.on("ResolveName",(a=>{"IMG"===a.target.nodeName&&e.dom.hasClass(a.target,r)&&(a.name="pagebreak")}))})(e)}))}(); -------------------------------------------------------------------------------- /public/tinymce/plugins/preview/plugin.min.js: -------------------------------------------------------------------------------- 1 | /** 2 | * TinyMCE version 7.6.1 (2025-01-22) 3 | */ 4 | !function(){"use strict";var e=tinymce.util.Tools.resolve("tinymce.PluginManager"),t=tinymce.util.Tools.resolve("tinymce.Env"),n=tinymce.util.Tools.resolve("tinymce.util.Tools");const o=e=>t=>t.options.get(e),i=o("content_style"),s=o("content_css_cors"),c=o("body_class"),r=o("body_id");e.add("preview",(e=>{(e=>{e.addCommand("mcePreview",(()=>{(e=>{const o=(e=>{var o;let a="";const l=e.dom.encode,d=null!==(o=i(e))&&void 0!==o?o:"";a+='';const m=s(e)?' crossorigin="anonymous"':"";n.each(e.contentCSS,(t=>{a+='"})),d&&(a+='");const y=r(e),u=c(e),v=' 24 | 25 | 26 | 27 | -------------------------------------------------------------------------------- /resources/views/templates/barcode-template.html: -------------------------------------------------------------------------------- 1 | 45 | 46 |
47 |
48 |
49 | "> 50 | 51 |
52 |
56 | 60 |
61 |
62 |
63 | <% if (barcode_settings.show_barcode_product_name === "on") { %> 64 |

71 | <%= product.name %> 72 |

73 | <% } %> 74 | <% if (barcode_settings.show_barcode_product_price === "on") { %> 75 |

84 | RS. <%= product.price %> 85 |

86 | <% } %> 87 |
88 |
-------------------------------------------------------------------------------- /resources/views/templates/barcode-xs-template.html: -------------------------------------------------------------------------------- 1 | 28 | 29 |
30 | <% if (barcode_settings.show_barcode_store === "on") { %> 31 |

32 | <%= shop_name %> 33 |

34 | <% } %> 35 | <% if (barcode_settings.show_barcode_product_price === "on") { %> 36 |

44 | RS. <%= product.price %> 45 |

46 | <% } %> 47 |
51 | 55 |
56 | <% if (barcode_settings.show_barcode_product_name === "on") { %> 57 |

64 | <%= product.name %> 65 |

66 | <% } %> 67 |
68 | 69 | -------------------------------------------------------------------------------- /resources/views/upload.blade.php: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Upload ZIP File for Upgrade 5 | 6 | 7 |

Upload ZIP File to Upgrade Application

8 | 9 | @if (session('success')) 10 |

{{ session('success') }}

11 | @endif 12 | 13 | @if (session('error')) 14 |

{{ session('error') }}

15 | @endif 16 | 17 |
18 | @csrf 19 | 20 | 21 | 22 |
23 | 24 | 25 | -------------------------------------------------------------------------------- /resources/views/vendor/authentication-log/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NifrasUsanar/InfoShop/229f52dbf7513ac71455111e334c18e9454dcd28/resources/views/vendor/authentication-log/.gitkeep -------------------------------------------------------------------------------- /resources/views/vendor/authentication-log/emails/failed.blade.php: -------------------------------------------------------------------------------- 1 | @component('mail::message') 2 | # @lang('Hello!') 3 | 4 | @lang('There has been a failed login attempt to your :app account.', ['app' => config('app.name')]) 5 | 6 | > **@lang('Account:')** {{ $account->email }}
7 | > **@lang('Time:')** {{ $time->toCookieString() }}
8 | > **@lang('IP Address:')** {{ $ipAddress }}
9 | > **@lang('Browser:')** {{ $browser }}
10 | @if ($location && $location['default'] === false) 11 | > **@lang('Location:')** {{ $location['city'] ?? __('Unknown City') }}, {{ $location['state'], __('Unknown State') }} 12 | @endif 13 | 14 | @lang('If this was you, you can ignore this alert. If you suspect any suspicious activity on your account, please change your password.') 15 | 16 | @lang('Regards,')
17 | {{ config('app.name') }} 18 | @endcomponent 19 | -------------------------------------------------------------------------------- /resources/views/vendor/authentication-log/emails/new.blade.php: -------------------------------------------------------------------------------- 1 | @component('mail::message') 2 | # @lang('Hello!') 3 | 4 | @lang('Your :app account logged in from a new device.', ['app' => config('app.name')]) 5 | 6 | > **@lang('Account:')** {{ $account->email }}
7 | > **@lang('Time:')** {{ $time->toCookieString() }}
8 | > **@lang('IP Address:')** {{ $ipAddress }}
9 | > **@lang('Browser:')** {{ $browser }}
10 | @if ($location && $location['default'] === false) 11 | > **@lang('Location:')** {{ $location['city'] ?? __('Unknown City') }}, {{ $location['state'], __('Unknown State') }} 12 | @endif 13 | 14 | @lang('If this was you, you can ignore this alert. If you suspect any suspicious activity on your account, please change your password.') 15 | 16 | @lang('Regards,')
17 | {{ config('app.name') }} 18 | @endcomponent 19 | -------------------------------------------------------------------------------- /routes/auth.php: -------------------------------------------------------------------------------- 1 | group(function () { 15 | Route::get('register', [RegisteredUserController::class, 'create']) 16 | ->name('register'); 17 | 18 | Route::post('register', [RegisteredUserController::class, 'store']); 19 | 20 | Route::get('login', [AuthenticatedSessionController::class, 'create']) 21 | ->name('login'); 22 | 23 | Route::post('login', [AuthenticatedSessionController::class, 'store']); 24 | 25 | Route::get('forgot-password', [PasswordResetLinkController::class, 'create']) 26 | ->name('password.request'); 27 | 28 | Route::post('forgot-password', [PasswordResetLinkController::class, 'store']) 29 | ->name('password.email'); 30 | 31 | Route::get('reset-password/{token}', [NewPasswordController::class, 'create']) 32 | ->name('password.reset'); 33 | 34 | Route::post('reset-password', [NewPasswordController::class, 'store']) 35 | ->name('password.store'); 36 | }); 37 | 38 | Route::middleware('auth')->group(function () { 39 | Route::get('verify-email', EmailVerificationPromptController::class) 40 | ->name('verification.notice'); 41 | 42 | Route::get('verify-email/{id}/{hash}', VerifyEmailController::class) 43 | ->middleware(['signed', 'throttle:6,1']) 44 | ->name('verification.verify'); 45 | 46 | Route::post('email/verification-notification', [EmailVerificationNotificationController::class, 'store']) 47 | ->middleware('throttle:6,1') 48 | ->name('verification.send'); 49 | 50 | Route::get('confirm-password', [ConfirmablePasswordController::class, 'show']) 51 | ->name('password.confirm'); 52 | 53 | Route::post('confirm-password', [ConfirmablePasswordController::class, 'store']); 54 | 55 | Route::put('password', [PasswordController::class, 'update'])->name('password.update'); 56 | 57 | Route::post('logout', [AuthenticatedSessionController::class, 'destroy']) 58 | ->name('logout'); 59 | }); 60 | -------------------------------------------------------------------------------- /routes/console.php: -------------------------------------------------------------------------------- 1 | comment(Inspiring::quote()); 8 | })->purpose('Display an inspiring quote')->hourly(); 9 | -------------------------------------------------------------------------------- /server config note.txt: -------------------------------------------------------------------------------- 1 | Server config 2 | --------------------------------------------------------------------------------- 3 | php artisan optimize:clear - we should run this command before deploying the site 4 | php artisan db:seed --class=ProductSeeder - if import needed 5 | npm run build 6 | 7 | upload the application without nodemodules 8 | make sure to keep template files in storage/app/public/templates folder [copy templates folder from the resources/views/templates] 9 | remove hot file from public folder to avoid vite client, so it takes build folder 10 | go to link-storage route to link the storage folder to public folder 11 | 12 | add an .htaccess file in root folder 13 | 14 | 15 | # That was ONLY to protect you from 500 errors 16 | # if your server did not have mod_rewrite enabled 17 | 18 | RewriteEngine On 19 | # RewriteBase / 20 | # NOT needed unless you're using mod_alias to redirect 21 | 22 | RewriteCond %{REQUEST_URI} !/public 23 | RewriteRule ^(.*)$ public/$1 [L] 24 | # Direct all requests to /public folder 25 | 26 | 27 | 28 | Feature update 29 | --------------------------------------------------------------------------------- 30 | composer update after adding a laravel package, 31 | then copy the vendor folder to the production server / copy the package folder and composer folder as in vendor folder and move it to the zip file -------------------------------------------------------------------------------- /storage/app/.gitignore: -------------------------------------------------------------------------------- 1 | * 2 | !private/ 3 | !public/ 4 | !.gitignore 5 | -------------------------------------------------------------------------------- /storage/app/private/.gitignore: -------------------------------------------------------------------------------- 1 | * 2 | !.gitignore 3 | -------------------------------------------------------------------------------- /storage/app/public/.gitignore: -------------------------------------------------------------------------------- 1 | * 2 | !.gitignore 3 | -------------------------------------------------------------------------------- /storage/framework/.gitignore: -------------------------------------------------------------------------------- 1 | compiled.php 2 | config.php 3 | down 4 | events.scanned.php 5 | maintenance.php 6 | routes.php 7 | routes.scanned.php 8 | schedule-* 9 | services.json 10 | -------------------------------------------------------------------------------- /storage/framework/cache/.gitignore: -------------------------------------------------------------------------------- 1 | * 2 | !data/ 3 | !.gitignore 4 | -------------------------------------------------------------------------------- /storage/framework/cache/data/.gitignore: -------------------------------------------------------------------------------- 1 | * 2 | !.gitignore 3 | -------------------------------------------------------------------------------- /storage/framework/sessions/.gitignore: -------------------------------------------------------------------------------- 1 | * 2 | !.gitignore 3 | -------------------------------------------------------------------------------- /storage/framework/testing/.gitignore: -------------------------------------------------------------------------------- 1 | * 2 | !.gitignore 3 | -------------------------------------------------------------------------------- /storage/framework/views/.gitignore: -------------------------------------------------------------------------------- 1 | * 2 | !.gitignore 3 | -------------------------------------------------------------------------------- /storage/logs/.gitignore: -------------------------------------------------------------------------------- 1 | * 2 | !.gitignore 3 | -------------------------------------------------------------------------------- /tailwind.config.js: -------------------------------------------------------------------------------- 1 | import defaultTheme from 'tailwindcss/defaultTheme'; 2 | import forms from '@tailwindcss/forms'; 3 | 4 | /** @type {import('tailwindcss').Config} */ 5 | export default { 6 | content: [ 7 | './vendor/laravel/framework/src/Illuminate/Pagination/resources/views/*.blade.php', 8 | './storage/framework/views/*.php', 9 | './resources/views/**/*.blade.php', 10 | './resources/js/**/*.jsx', 11 | ], 12 | safelist: [ 13 | {pattern: /flex-./}, 14 | ], 15 | 16 | theme: { 17 | extend: { 18 | fontFamily: { 19 | sans: ['Figtree', ...defaultTheme.fontFamily.sans], 20 | }, 21 | }, 22 | }, 23 | 24 | plugins: [forms], 25 | }; 26 | -------------------------------------------------------------------------------- /tests/Feature/Auth/AuthenticationTest.php: -------------------------------------------------------------------------------- 1 | get('/login'); 16 | 17 | $response->assertStatus(200); 18 | } 19 | 20 | public function test_users_can_authenticate_using_the_login_screen(): void 21 | { 22 | $user = User::factory()->create(); 23 | 24 | $response = $this->post('/login', [ 25 | 'email' => $user->email, 26 | 'password' => 'password', 27 | ]); 28 | 29 | $this->assertAuthenticated(); 30 | $response->assertRedirect(route('dashboard', absolute: false)); 31 | } 32 | 33 | public function test_users_can_not_authenticate_with_invalid_password(): void 34 | { 35 | $user = User::factory()->create(); 36 | 37 | $this->post('/login', [ 38 | 'email' => $user->email, 39 | 'password' => 'wrong-password', 40 | ]); 41 | 42 | $this->assertGuest(); 43 | } 44 | 45 | public function test_users_can_logout(): void 46 | { 47 | $user = User::factory()->create(); 48 | 49 | $response = $this->actingAs($user)->post('/logout'); 50 | 51 | $this->assertGuest(); 52 | $response->assertRedirect('/'); 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /tests/Feature/Auth/EmailVerificationTest.php: -------------------------------------------------------------------------------- 1 | unverified()->create(); 19 | 20 | $response = $this->actingAs($user)->get('/verify-email'); 21 | 22 | $response->assertStatus(200); 23 | } 24 | 25 | public function test_email_can_be_verified(): void 26 | { 27 | $user = User::factory()->unverified()->create(); 28 | 29 | Event::fake(); 30 | 31 | $verificationUrl = URL::temporarySignedRoute( 32 | 'verification.verify', 33 | now()->addMinutes(60), 34 | ['id' => $user->id, 'hash' => sha1($user->email)] 35 | ); 36 | 37 | $response = $this->actingAs($user)->get($verificationUrl); 38 | 39 | Event::assertDispatched(Verified::class); 40 | $this->assertTrue($user->fresh()->hasVerifiedEmail()); 41 | $response->assertRedirect(route('dashboard', absolute: false).'?verified=1'); 42 | } 43 | 44 | public function test_email_is_not_verified_with_invalid_hash(): void 45 | { 46 | $user = User::factory()->unverified()->create(); 47 | 48 | $verificationUrl = URL::temporarySignedRoute( 49 | 'verification.verify', 50 | now()->addMinutes(60), 51 | ['id' => $user->id, 'hash' => sha1('wrong-email')] 52 | ); 53 | 54 | $this->actingAs($user)->get($verificationUrl); 55 | 56 | $this->assertFalse($user->fresh()->hasVerifiedEmail()); 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /tests/Feature/Auth/PasswordConfirmationTest.php: -------------------------------------------------------------------------------- 1 | create(); 16 | 17 | $response = $this->actingAs($user)->get('/confirm-password'); 18 | 19 | $response->assertStatus(200); 20 | } 21 | 22 | public function test_password_can_be_confirmed(): void 23 | { 24 | $user = User::factory()->create(); 25 | 26 | $response = $this->actingAs($user)->post('/confirm-password', [ 27 | 'password' => 'password', 28 | ]); 29 | 30 | $response->assertRedirect(); 31 | $response->assertSessionHasNoErrors(); 32 | } 33 | 34 | public function test_password_is_not_confirmed_with_invalid_password(): void 35 | { 36 | $user = User::factory()->create(); 37 | 38 | $response = $this->actingAs($user)->post('/confirm-password', [ 39 | 'password' => 'wrong-password', 40 | ]); 41 | 42 | $response->assertSessionHasErrors(); 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /tests/Feature/Auth/PasswordResetTest.php: -------------------------------------------------------------------------------- 1 | get('/forgot-password'); 18 | 19 | $response->assertStatus(200); 20 | } 21 | 22 | public function test_reset_password_link_can_be_requested(): void 23 | { 24 | Notification::fake(); 25 | 26 | $user = User::factory()->create(); 27 | 28 | $this->post('/forgot-password', ['email' => $user->email]); 29 | 30 | Notification::assertSentTo($user, ResetPassword::class); 31 | } 32 | 33 | public function test_reset_password_screen_can_be_rendered(): void 34 | { 35 | Notification::fake(); 36 | 37 | $user = User::factory()->create(); 38 | 39 | $this->post('/forgot-password', ['email' => $user->email]); 40 | 41 | Notification::assertSentTo($user, ResetPassword::class, function ($notification) { 42 | $response = $this->get('/reset-password/'.$notification->token); 43 | 44 | $response->assertStatus(200); 45 | 46 | return true; 47 | }); 48 | } 49 | 50 | public function test_password_can_be_reset_with_valid_token(): void 51 | { 52 | Notification::fake(); 53 | 54 | $user = User::factory()->create(); 55 | 56 | $this->post('/forgot-password', ['email' => $user->email]); 57 | 58 | Notification::assertSentTo($user, ResetPassword::class, function ($notification) use ($user) { 59 | $response = $this->post('/reset-password', [ 60 | 'token' => $notification->token, 61 | 'email' => $user->email, 62 | 'password' => 'password', 63 | 'password_confirmation' => 'password', 64 | ]); 65 | 66 | $response 67 | ->assertSessionHasNoErrors() 68 | ->assertRedirect(route('login')); 69 | 70 | return true; 71 | }); 72 | } 73 | } 74 | -------------------------------------------------------------------------------- /tests/Feature/Auth/PasswordUpdateTest.php: -------------------------------------------------------------------------------- 1 | create(); 17 | 18 | $response = $this 19 | ->actingAs($user) 20 | ->from('/profile') 21 | ->put('/password', [ 22 | 'current_password' => 'password', 23 | 'password' => 'new-password', 24 | 'password_confirmation' => 'new-password', 25 | ]); 26 | 27 | $response 28 | ->assertSessionHasNoErrors() 29 | ->assertRedirect('/profile'); 30 | 31 | $this->assertTrue(Hash::check('new-password', $user->refresh()->password)); 32 | } 33 | 34 | public function test_correct_password_must_be_provided_to_update_password(): void 35 | { 36 | $user = User::factory()->create(); 37 | 38 | $response = $this 39 | ->actingAs($user) 40 | ->from('/profile') 41 | ->put('/password', [ 42 | 'current_password' => 'wrong-password', 43 | 'password' => 'new-password', 44 | 'password_confirmation' => 'new-password', 45 | ]); 46 | 47 | $response 48 | ->assertSessionHasErrors('current_password') 49 | ->assertRedirect('/profile'); 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /tests/Feature/Auth/RegistrationTest.php: -------------------------------------------------------------------------------- 1 | get('/register'); 15 | 16 | $response->assertStatus(200); 17 | } 18 | 19 | public function test_new_users_can_register(): void 20 | { 21 | $response = $this->post('/register', [ 22 | 'name' => 'Test User', 23 | 'email' => 'test@example.com', 24 | 'password' => 'password', 25 | 'password_confirmation' => 'password', 26 | ]); 27 | 28 | $this->assertAuthenticated(); 29 | $response->assertRedirect(route('dashboard', absolute: false)); 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /tests/Feature/ExampleTest.php: -------------------------------------------------------------------------------- 1 | get('/'); 16 | 17 | $response->assertStatus(200); 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /tests/Feature/ProfileTest.php: -------------------------------------------------------------------------------- 1 | create(); 16 | 17 | $response = $this 18 | ->actingAs($user) 19 | ->get('/profile'); 20 | 21 | $response->assertOk(); 22 | } 23 | 24 | public function test_profile_information_can_be_updated(): void 25 | { 26 | $user = User::factory()->create(); 27 | 28 | $response = $this 29 | ->actingAs($user) 30 | ->patch('/profile', [ 31 | 'name' => 'Test User', 32 | 'email' => 'test@example.com', 33 | ]); 34 | 35 | $response 36 | ->assertSessionHasNoErrors() 37 | ->assertRedirect('/profile'); 38 | 39 | $user->refresh(); 40 | 41 | $this->assertSame('Test User', $user->name); 42 | $this->assertSame('test@example.com', $user->email); 43 | $this->assertNull($user->email_verified_at); 44 | } 45 | 46 | public function test_email_verification_status_is_unchanged_when_the_email_address_is_unchanged(): void 47 | { 48 | $user = User::factory()->create(); 49 | 50 | $response = $this 51 | ->actingAs($user) 52 | ->patch('/profile', [ 53 | 'name' => 'Test User', 54 | 'email' => $user->email, 55 | ]); 56 | 57 | $response 58 | ->assertSessionHasNoErrors() 59 | ->assertRedirect('/profile'); 60 | 61 | $this->assertNotNull($user->refresh()->email_verified_at); 62 | } 63 | 64 | public function test_user_can_delete_their_account(): void 65 | { 66 | $user = User::factory()->create(); 67 | 68 | $response = $this 69 | ->actingAs($user) 70 | ->delete('/profile', [ 71 | 'password' => 'password', 72 | ]); 73 | 74 | $response 75 | ->assertSessionHasNoErrors() 76 | ->assertRedirect('/'); 77 | 78 | $this->assertGuest(); 79 | $this->assertNull($user->fresh()); 80 | } 81 | 82 | public function test_correct_password_must_be_provided_to_delete_account(): void 83 | { 84 | $user = User::factory()->create(); 85 | 86 | $response = $this 87 | ->actingAs($user) 88 | ->from('/profile') 89 | ->delete('/profile', [ 90 | 'password' => 'wrong-password', 91 | ]); 92 | 93 | $response 94 | ->assertSessionHasErrors('password') 95 | ->assertRedirect('/profile'); 96 | 97 | $this->assertNotNull($user->fresh()); 98 | } 99 | } 100 | -------------------------------------------------------------------------------- /tests/TestCase.php: -------------------------------------------------------------------------------- 1 | assertTrue(true); 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /vite.config.js: -------------------------------------------------------------------------------- 1 | import { defineConfig } from 'vite'; 2 | import laravel from 'laravel-vite-plugin'; 3 | import react from '@vitejs/plugin-react'; 4 | 5 | export default defineConfig({ 6 | plugins: [ 7 | laravel({ 8 | input: 'resources/js/app.jsx', 9 | refresh: true, 10 | }), 11 | react(), 12 | ], 13 | }); 14 | --------------------------------------------------------------------------------