├── routes ├── jarvis.php ├── channels.php ├── console.php └── api.php ├── database ├── .gitignore ├── seeders │ ├── DatabaseSeeder.php │ ├── SettingSeeder.php │ ├── UserSeeder.php │ ├── PermissionSeeder.php │ └── RoleSeeder.php ├── migrations │ ├── 2014_10_12_100000_create_password_reset_tokens_table.php │ ├── 2023_08_21_033159_add_event_column_to_activity_log_table.php │ ├── 2023_08_21_033200_add_batch_uuid_column_to_activity_log_table.php │ ├── 2019_08_19_000000_create_failed_jobs_table.php │ ├── 2023_03_17_065919_create_sessions_table.php │ ├── 2023_03_30_111713_create_settings_table.php │ ├── 2019_12_14_000001_create_personal_access_tokens_table.php │ ├── 2014_10_12_000000_create_users_table.php │ ├── 2023_08_21_033158_create_activity_log_table.php │ └── 2014_10_12_200000_add_two_factor_columns_to_users_table.php └── factories │ └── UserFactory.php ├── bootstrap ├── cache │ └── .gitignore └── app.php ├── storage ├── logs │ └── .gitignore ├── pail │ └── .gitignore ├── app │ ├── public │ │ └── .gitignore │ └── .gitignore └── framework │ ├── testing │ └── .gitignore │ ├── views │ └── .gitignore │ ├── cache │ ├── data │ │ └── .gitignore │ └── .gitignore │ ├── sessions │ └── .gitignore │ └── .gitignore ├── public ├── robots.txt ├── favicon.ico ├── favicon.png ├── build │ └── assets │ │ ├── _plugin-vue_export-helper-c27b6911.js │ │ ├── AppLayout-9096ccf1.css │ │ ├── InputError-71f77be1.js │ │ ├── AuthenticationCardLogo-a2386df2.js │ │ ├── ApplicationLogo-914092fa.js │ │ ├── SectionBorder-6e176b61.js │ │ ├── InputLabel-ef1a5601.js │ │ ├── ActionMessage-dafe95ee.js │ │ ├── ActionSection-bb60ff24.js │ │ ├── SectionTitle-1f89132a.js │ │ ├── DangerButton-db69db22.js │ │ ├── Breadcrumb-ad958516.js │ │ ├── SecondaryButton-4d541c38.js │ │ ├── PrimaryButton-0fb1f193.js │ │ ├── Checkbox-97f2cde6.js │ │ ├── Welcome-665689a9.css │ │ ├── PrivacyPolicy-147c9bb3.js │ │ ├── TermsOfService-8f4b5ac0.js │ │ ├── Properties-5d7d447e.js │ │ ├── TextInput-f97bd710.js │ │ ├── FormSection-fd684e6c.js │ │ ├── SwitchLocale-b9e142e8.js │ │ ├── SelectInput-188689a8.js │ │ ├── DialogModal-9a2b4a3d.js │ │ ├── Index-67a1ca5c.js │ │ ├── Delete-79d257e4.js │ │ ├── Delete-a6494205.js │ │ ├── Delete-787e6cee.js │ │ ├── DeleteBulk-6f56dab6.js │ │ ├── DeleteBulk-896ff391.js │ │ ├── DeleteBulk-d065b9a7.js │ │ ├── DeleteBulk-f59ba241.js │ │ ├── Delete-9c183e52.js │ │ ├── Permission-e0ed2ecc.js │ │ ├── ConfirmationModal-8097dc2a.js │ │ ├── Create-1f78ecde.js │ │ ├── ConfirmPassword-534de2b7.js │ │ ├── ForgotPassword-3b3afe78.js │ │ ├── Show-c2090c6a.js │ │ ├── Modal-d953ae84.js │ │ ├── DeleteUserForm-a3ec2a2d.js │ │ ├── Edit-d7112c95.js │ │ └── VerifyEmail-fb5fdae9.js ├── logo.svg ├── .htaccess ├── favicon.svg └── index.php ├── postcss.config.js ├── resources ├── markdown │ ├── policy.md │ └── terms.md ├── js │ ├── Components │ │ ├── ApplicationLogo.vue │ │ ├── SectionBorder.vue │ │ ├── InputError.vue │ │ ├── AuthenticationCardLogo.vue │ │ ├── InputLabel.vue │ │ ├── ActionMessage.vue │ │ ├── Guest │ │ │ └── NavbarLink.vue │ │ ├── SectionTitle.vue │ │ ├── DangerButton.vue │ │ ├── SecondaryButton.vue │ │ ├── MenuLabel.vue │ │ ├── PrimaryButton.vue │ │ ├── ActionSection.vue │ │ ├── BackToTop.vue │ │ ├── SwitchDarkMode.vue │ │ ├── Checkbox.vue │ │ ├── NavLink.vue │ │ ├── Table.vue │ │ ├── SwitchLocale.vue │ │ ├── TextInput.vue │ │ ├── TextAreaInput.vue │ │ ├── Offline.vue │ │ ├── DropdownLink.vue │ │ ├── SelectInput.vue │ │ ├── ResponsiveNavLink.vue │ │ ├── FormSection.vue │ │ ├── AuthenticationCard.vue │ │ ├── DialogModal.vue │ │ ├── ConfirmationModal.vue │ │ └── DeleteComponent.vue │ ├── Mixins │ │ └── global.js │ ├── Pages │ │ ├── API │ │ │ └── Index.vue │ │ ├── PrivacyPolicy.vue │ │ ├── TermsOfService.vue │ │ ├── Activity │ │ │ └── Properties.vue │ │ ├── Guest │ │ │ └── Index.vue │ │ ├── Auth │ │ │ ├── ForgotPassword.vue │ │ │ └── ConfirmPassword.vue │ │ ├── Profile │ │ │ └── Show.vue │ │ └── User │ │ │ └── Delete.vue │ ├── Layouts │ │ ├── Authenticated │ │ │ ├── Breadcrumb.vue │ │ │ └── Footer.vue │ │ ├── GuestLayout.vue │ │ ├── Guest │ │ │ └── Footer.vue │ │ └── AppLayout.vue │ ├── ssr.js │ ├── bootstrap.js │ └── app.js ├── stubs │ ├── Route.stub │ ├── Requests │ │ ├── Store.stub │ │ ├── Update.stub │ │ └── Index.stub │ ├── Migration.stub │ └── Model.stub └── views │ ├── cli │ └── pages.blade.php │ ├── emails │ └── team-invitation.blade.php │ └── app.blade.php ├── lang ├── id │ ├── pagination.php │ ├── auth.php │ └── passwords.php └── en │ ├── pagination.php │ ├── auth.php │ └── passwords.php ├── tests ├── TestCase.php ├── Unit │ └── ExampleTest.php ├── Feature │ ├── ExampleTest.php │ ├── BrowserSessionsTest.php │ ├── ProfileInformationTest.php │ ├── DeleteApiTokenTest.php │ ├── CreateApiTokenTest.php │ ├── AuthenticationTest.php │ ├── DeleteAccountTest.php │ ├── PasswordConfirmationTest.php │ ├── ApiTokenPermissionsTest.php │ ├── UpdatePasswordTest.php │ └── RegistrationTest.php └── CreatesApplication.php ├── .gitattributes ├── .editorconfig ├── app ├── Http │ ├── Controllers │ │ ├── GuestController.php │ │ ├── Controller.php │ │ ├── DashboardController.php │ │ └── Api │ │ │ └── UserController.php │ ├── Middleware │ │ ├── EncryptCookies.php │ │ ├── VerifyCsrfToken.php │ │ ├── PreventRequestsDuringMaintenance.php │ │ ├── TrimStrings.php │ │ ├── TrustHosts.php │ │ ├── Authenticate.php │ │ ├── ValidateSignature.php │ │ ├── Localization.php │ │ ├── TrustProxies.php │ │ └── RedirectIfAuthenticated.php │ └── Requests │ │ ├── Permission │ │ ├── PermissionUpdateRequest.php │ │ ├── PermissionStoreRequest.php │ │ └── PermissionIndexRequest.php │ │ ├── Role │ │ ├── RoleUpdateRequest.php │ │ ├── RoleStoreRequest.php │ │ └── RoleIndexRequest.php │ │ ├── User │ │ ├── UserIndexRequest.php │ │ ├── UserStoreRequest.php │ │ └── UserUpdateRequest.php │ │ └── Activity │ │ └── ActivityIndexRequest.php ├── Actions │ ├── Jetstream │ │ └── DeleteUser.php │ └── Fortify │ │ ├── PasswordValidationRules.php │ │ ├── ResetUserPassword.php │ │ ├── UpdateUserPassword.php │ │ ├── CreateNewUser.php │ │ └── UpdateUserProfileInformation.php ├── Providers │ ├── BroadcastServiceProvider.php │ ├── AppServiceProvider.php │ ├── AuthServiceProvider.php │ ├── EventServiceProvider.php │ ├── JetstreamServiceProvider.php │ ├── RouteServiceProvider.php │ └── FortifyServiceProvider.php ├── Console │ └── Kernel.php └── Exceptions │ └── Handler.php ├── .gitignore ├── config ├── cors.php ├── services.php ├── view.php ├── activitylog.php └── hashing.php ├── package.json ├── phpunit.xml ├── vite.config.js ├── .env.example └── artisan /routes/jarvis.php: -------------------------------------------------------------------------------- 1 | {const o=t.__vccOpts||t;for(const[c,e]of r)o[c]=e;return o};export{s as _}; 2 | -------------------------------------------------------------------------------- /lang/id/pagination.php: -------------------------------------------------------------------------------- 1 | '»', 7 | 'previous' => '«', 8 | ]; 9 | -------------------------------------------------------------------------------- /lang/en/pagination.php: -------------------------------------------------------------------------------- 1 | 'Next »', 7 | 'previous' => '« Previous', 8 | ]; 9 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /tests/TestCase.php: -------------------------------------------------------------------------------- 1 | 2 | 3 | 9 | -------------------------------------------------------------------------------- /.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 | -------------------------------------------------------------------------------- /resources/js/Components/SectionBorder.vue: -------------------------------------------------------------------------------- 1 | 8 | -------------------------------------------------------------------------------- /lang/en/auth.php: -------------------------------------------------------------------------------- 1 | 'These credentials do not match our records.', 7 | 'password' => 'The password is incorrect.', 8 | 'throttle' => 'Too many login attempts. Please try again in :seconds seconds.', 9 | ]; 10 | -------------------------------------------------------------------------------- /lang/id/auth.php: -------------------------------------------------------------------------------- 1 | 'Identitas tersebut tidak cocok dengan data kami.', 7 | 'password' => 'Kata sandi salah.', 8 | 'throttle' => 'Terlalu banyak upaya masuk. Silahkan coba lagi dalam :seconds detik.', 9 | ]; 10 | -------------------------------------------------------------------------------- /public/build/assets/InputError-71f77be1.js: -------------------------------------------------------------------------------- 1 | import{j as s,k as t,o as a,d as r,a as o,t as c}from"./app-9cb26ff6.js";const n={class:"text-sm text-rose-600 dark:text-rose-400"},p={__name:"InputError",props:{message:String},setup(e){return(i,m)=>s((a(),r("div",null,[o("p",n,c(e.message),1)],512)),[[t,e.message]])}};export{p as _}; 2 | -------------------------------------------------------------------------------- /resources/js/Components/InputError.vue: -------------------------------------------------------------------------------- 1 | 6 | 7 | 14 | -------------------------------------------------------------------------------- /public/build/assets/AuthenticationCardLogo-a2386df2.js: -------------------------------------------------------------------------------- 1 | import{o as a,c as o,w as t,b as e,u as r,x as s}from"./app-9cb26ff6.js";import{A as c}from"./ApplicationLogo-914092fa.js";const f={__name:"AuthenticationCardLogo",setup(n){return(_,p)=>(a(),o(r(s),{href:"/"},{default:t(()=>[e(c,{class:"h-auto w-8"})]),_:1}))}};export{f as _}; 2 | -------------------------------------------------------------------------------- /resources/js/Components/AuthenticationCardLogo.vue: -------------------------------------------------------------------------------- 1 | 5 | 6 | 11 | -------------------------------------------------------------------------------- /tests/Unit/ExampleTest.php: -------------------------------------------------------------------------------- 1 | assertTrue(true); 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /.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 | -------------------------------------------------------------------------------- /public/build/assets/ApplicationLogo-914092fa.js: -------------------------------------------------------------------------------- 1 | import{_ as e}from"./_plugin-vue_export-helper-c27b6911.js";import{o as t,d as p}from"./app-9cb26ff6.js";const r={},s=["src","alt"];function a(o,n){return t(),p("img",{src:o.$page.props.app.setting.full_path_logo,alt:"Logo "+o.$page.props.app.setting.name},null,8,s)}const _=e(r,[["render",a]]);export{_ as A}; 2 | -------------------------------------------------------------------------------- /app/Http/Controllers/GuestController.php: -------------------------------------------------------------------------------- 1 | "Home", 13 | ]); 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /public/build/assets/SectionBorder-6e176b61.js: -------------------------------------------------------------------------------- 1 | import{_ as o}from"./_plugin-vue_export-helper-c27b6911.js";import{o as s,d as r,a as e}from"./app-9cb26ff6.js";const t={},c={class:"hidden sm:block"},a=e("div",{class:"py-8"},[e("div",{class:"border-t border-slate-200 dark:border-slate-700"})],-1),d=[a];function n(_,i){return s(),r("div",c,d)}const f=o(t,[["render",n]]);export{f as S}; 2 | -------------------------------------------------------------------------------- /public/build/assets/InputLabel-ef1a5601.js: -------------------------------------------------------------------------------- 1 | import{o as t,d as e,t as o,A as l}from"./app-9cb26ff6.js";const n={class:"block font-medium text-sm text-slate-700 dark:text-slate-300"},r={key:0},c={key:1},u={__name:"InputLabel",props:{value:String},setup(s){return(a,_)=>(t(),e("label",n,[s.value?(t(),e("span",r,o(s.value),1)):(t(),e("span",c,[l(a.$slots,"default")]))]))}};export{u as _}; 2 | -------------------------------------------------------------------------------- /app/Http/Controllers/Controller.php: -------------------------------------------------------------------------------- 1 | 2 | defineProps({ 3 | value: String, 4 | }); 5 | 6 | 7 | 15 | -------------------------------------------------------------------------------- /resources/stubs/Route.stub: -------------------------------------------------------------------------------- 1 | except('create', 'show', 'edit'); 7 | Route::post('{{modelNameLowerCase}}/destroy-bulk', [{{modelName}}Controller::class, 'destroyBulk'])->name('{{modelNameLowerCase}}.destroy-bulk'); -------------------------------------------------------------------------------- /lang/en/passwords.php: -------------------------------------------------------------------------------- 1 | 'Your password has been reset!', 7 | 'sent' => 'We have emailed your password reset link!', 8 | 'throttled' => 'Please wait before retrying.', 9 | 'token' => 'This password reset token is invalid.', 10 | 'user' => 'We can\'t find a user with that email address.', 11 | ]; 12 | -------------------------------------------------------------------------------- /app/Http/Middleware/EncryptCookies.php: -------------------------------------------------------------------------------- 1 | 13 | */ 14 | protected $except = [ 15 | // 16 | ]; 17 | } 18 | -------------------------------------------------------------------------------- /app/Http/Middleware/VerifyCsrfToken.php: -------------------------------------------------------------------------------- 1 | 13 | */ 14 | protected $except = [ 15 | // 16 | ]; 17 | } 18 | -------------------------------------------------------------------------------- /lang/id/passwords.php: -------------------------------------------------------------------------------- 1 | 'Kata sandi Anda sudah direset!', 7 | 'sent' => 'Kami sudah mengirim surel yang berisi tautan untuk mereset kata sandi Anda!', 8 | 'throttled' => 'Harap tunggu sebelum mencoba lagi.', 9 | 'token' => 'Token pengaturan ulang kata sandi tidak sah.', 10 | 'user' => 'Kami tidak dapat menemukan pengguna dengan alamat surel tersebut.', 11 | ]; 12 | -------------------------------------------------------------------------------- /tests/Feature/ExampleTest.php: -------------------------------------------------------------------------------- 1 | get('/'); 16 | 17 | $response->assertStatus(200); 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /app/Actions/Jetstream/DeleteUser.php: -------------------------------------------------------------------------------- 1 | deleteProfilePhoto(); 16 | $user->tokens->each->delete(); 17 | $user->delete(); 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /app/Providers/BroadcastServiceProvider.php: -------------------------------------------------------------------------------- 1 | 13 | */ 14 | protected $except = [ 15 | // 16 | ]; 17 | } 18 | -------------------------------------------------------------------------------- /app/Http/Middleware/TrimStrings.php: -------------------------------------------------------------------------------- 1 | 13 | */ 14 | protected $except = [ 15 | 'current_password', 16 | 'password', 17 | 'password_confirmation', 18 | ]; 19 | } 20 | -------------------------------------------------------------------------------- /public/build/assets/ActionMessage-dafe95ee.js: -------------------------------------------------------------------------------- 1 | import{o as t,d as s,b as o,w as n,j as c,a as i,A as l,k as r,T as d}from"./app-9cb26ff6.js";const _={class:"text-sm text-slate-600 dark:text-slate-400"},m={__name:"ActionMessage",props:{on:Boolean},setup(e){return(a,p)=>(t(),s("div",null,[o(d,{"leave-active-class":"transition ease-in duration-1000","leave-from-class":"opacity-100","leave-to-class":"opacity-0"},{default:n(()=>[c(i("div",_,[l(a.$slots,"default")],512),[[r,e.on]])]),_:3})]))}};export{m as _}; 2 | -------------------------------------------------------------------------------- /tests/CreatesApplication.php: -------------------------------------------------------------------------------- 1 | make(Kernel::class)->bootstrap(); 18 | 19 | return $app; 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /app/Http/Middleware/TrustHosts.php: -------------------------------------------------------------------------------- 1 | 13 | */ 14 | public function hosts(): array 15 | { 16 | return [ 17 | $this->allSubdomainsOfApplicationUrl(), 18 | ]; 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /app/Providers/AppServiceProvider.php: -------------------------------------------------------------------------------- 1 | 2 | defineProps({ 3 | on: Boolean, 4 | }); 5 | 6 | 7 | 17 | -------------------------------------------------------------------------------- /app/Actions/Fortify/PasswordValidationRules.php: -------------------------------------------------------------------------------- 1 | 13 | */ 14 | protected function passwordRules(): array 15 | { 16 | return ['required', 'string', new Password, 'confirmed']; 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /app/Http/Middleware/Authenticate.php: -------------------------------------------------------------------------------- 1 | expectsJson() ? null : route('login'); 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /public/build/assets/ActionSection-bb60ff24.js: -------------------------------------------------------------------------------- 1 | import{S as a}from"./SectionTitle-1f89132a.js";import{o as d,d as i,b as c,w as e,A as t,a as o}from"./app-9cb26ff6.js";const n={class:"md:grid md:grid-cols-3 md:gap-6"},r={class:"mt-5 md:mt-0 md:col-span-2"},l={class:"px-4 py-5 sm:p-6 bg-white dark:bg-slate-800 shadow sm:rounded"},g={__name:"ActionSection",setup(m){return(s,p)=>(d(),i("div",n,[c(a,null,{title:e(()=>[t(s.$slots,"title")]),description:e(()=>[t(s.$slots,"description")]),_:3}),o("div",r,[o("div",l,[t(s.$slots,"content")])])]))}};export{g as _}; 2 | -------------------------------------------------------------------------------- /resources/js/Components/Guest/NavbarLink.vue: -------------------------------------------------------------------------------- 1 | 9 | 16 | -------------------------------------------------------------------------------- /app/Http/Controllers/DashboardController.php: -------------------------------------------------------------------------------- 1 | User::count(), 16 | 'roleCount' => Role::count(), 17 | 'permissionCount' => Permission::count() 18 | ]); 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /public/build/assets/SectionTitle-1f89132a.js: -------------------------------------------------------------------------------- 1 | import{_ as o}from"./_plugin-vue_export-helper-c27b6911.js";import{o as a,d as c,a as s,A as e}from"./app-9cb26ff6.js";const l={},n={class:"md:col-span-1 flex justify-between"},i={class:"px-4 sm:px-0"},d={class:"text-lg font-medium text-slate-900 dark:text-slate-100"},r={class:"mt-1 text-sm text-slate-600 dark:text-slate-400"},_={class:"px-4 sm:px-0"};function m(t,p){return a(),c("div",n,[s("div",i,[s("h3",d,[e(t.$slots,"title")]),s("p",r,[e(t.$slots,"description")])]),s("div",_,[e(t.$slots,"aside")])])}const h=o(l,[["render",m]]);export{h as S}; 2 | -------------------------------------------------------------------------------- /app/Http/Middleware/ValidateSignature.php: -------------------------------------------------------------------------------- 1 | 13 | */ 14 | protected $except = [ 15 | // 'fbclid', 16 | // 'utm_campaign', 17 | // 'utm_content', 18 | // 'utm_medium', 19 | // 'utm_source', 20 | // 'utm_term', 21 | ]; 22 | } 23 | -------------------------------------------------------------------------------- /resources/js/Components/SectionTitle.vue: -------------------------------------------------------------------------------- 1 | 18 | -------------------------------------------------------------------------------- /public/build/assets/DangerButton-db69db22.js: -------------------------------------------------------------------------------- 1 | import{o,d as r,A as s}from"./app-9cb26ff6.js";const n=["type"],c={__name:"DangerButton",props:{type:{type:String,default:"button"}},setup(e){return(t,a)=>(o(),r("button",{type:e.type,class:"inline-flex items-center justify-center px-4 py-2 bg-rose-600 border border-transparent rounded font-semibold text-xs text-white uppercase tracking-widest hover:bg-rose-500 active:bg-rose-700 focus:outline-none focus:ring-2 focus:ring-rose-500 focus:ring-offset-2 dark:focus:ring-offset-slate-800 transition ease-in-out duration-150"},[s(t.$slots,"default")],8,n))}};export{c as _}; 2 | -------------------------------------------------------------------------------- /public/logo.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /public/build/assets/Breadcrumb-ad958516.js: -------------------------------------------------------------------------------- 1 | import{i as o}from"./index-afa3307c.js";import{o as e,d as t,F as l,f as i,b as a,u as r,w as d,e as m,t as f,x as u}from"./app-9cb26ff6.js";const x={class:"hidden md:flex justify-start items-center space-x-2 text-slate-500 dark:text-slate-200 text-sm font-normal"},k={__name:"Breadcrumb",props:{breadcrumbs:{type:Object,default:[]}},setup(c){return(p,h)=>(e(),t("div",x,[(e(!0),t(l,null,i(c.breadcrumbs,(s,n)=>(e(),t("div",{key:n,class:"flex items-center space-x-2"},[a(r(o),{class:"w-3 h-3"}),a(r(u),{href:s.href},{default:d(()=>[m(f(s.label),1)]),_:2},1032,["href"])]))),128))]))}};export{k as _}; 2 | -------------------------------------------------------------------------------- /resources/js/Components/DangerButton.vue: -------------------------------------------------------------------------------- 1 | 9 | 10 | 16 | -------------------------------------------------------------------------------- /routes/channels.php: -------------------------------------------------------------------------------- 1 | id === (int) $id; 18 | }); 19 | -------------------------------------------------------------------------------- /tests/Feature/BrowserSessionsTest.php: -------------------------------------------------------------------------------- 1 | actingAs($user = User::factory()->create()); 16 | 17 | $response = $this->delete('/user/other-browser-sessions', [ 18 | 'password' => 'password', 19 | ]); 20 | 21 | $response->assertSessionHasNoErrors(); 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /public/build/assets/SecondaryButton-4d541c38.js: -------------------------------------------------------------------------------- 1 | import{o,d as s,A as r}from"./app-9cb26ff6.js";const a=["type"],d={__name:"SecondaryButton",props:{type:{type:String,default:"button"}},setup(e){return(t,n)=>(o(),s("button",{type:e.type,class:"inline-flex items-center px-4 py-2 bg-white dark:bg-slate-800 border border-slate-300 dark:border-slate-500 rounded font-semibold text-xs text-slate-700 dark:text-slate-300 uppercase tracking-widest shadow-sm hover:bg-slate-50 dark:hover:bg-slate-700 focus:outline-none focus:ring-2 focus:ring-primary focus:ring-offset-2 dark:focus:ring-offset-slate-800 disabled:opacity-25 transition ease-in-out duration-150"},[r(t.$slots,"default")],8,a))}};export{d as _}; 2 | -------------------------------------------------------------------------------- /routes/console.php: -------------------------------------------------------------------------------- 1 | comment(Inspiring::quote()); 19 | })->purpose('Display an inspiring quote'); 20 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /.phpunit.cache 2 | /bootstrap/ssr 3 | /node_modules 4 | # /public/build 5 | /public/hot 6 | /public/storage 7 | /storage/*.key 8 | /vendor 9 | .env 10 | .env.backup 11 | .env.production 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 | 22 | #Add by AI 23 | .DS_Store 24 | *.DS_Store 25 | Thumbs.db 26 | ehthumbs.db 27 | Desktop.ini 28 | .AppleDouble 29 | .LSOverride 30 | Icon 31 | .Spotlight-V100 32 | ._* 33 | .Trashes 34 | ._.DS_Store 35 | .sass-cache 36 | npm-debug.log.* 37 | .env.local 38 | .env.test.local 39 | .env.development.local 40 | .env.production.local 41 | -------------------------------------------------------------------------------- /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/build/assets/PrimaryButton-0fb1f193.js: -------------------------------------------------------------------------------- 1 | import{o as t,d as a,A as o}from"./app-9cb26ff6.js";const i=["type"],p={__name:"PrimaryButton",props:{type:{type:String,default:"submit"}},setup(r){return(e,s)=>(t(),a("button",{type:r.type,class:"inline-flex items-center px-4 py-2 bg-primary dark:bg-primary border border-transparent rounded font-semibold text-xs text-white uppercase tracking-widest hover:bg-primary/90 dark:hover:bg-primary/80 focus:bg-primary dark:focus:bg-primary/80 active:bg-primary/60 dark:active:bg-primary/80 focus:outline-none focus:ring-2 focus:ring-primary focus:ring-offset-2 dark:focus:ring-offset-slate-800 transition ease-in-out duration-150"},[o(e.$slots,"default")],8,i))}};export{p as _}; 2 | -------------------------------------------------------------------------------- /app/Console/Kernel.php: -------------------------------------------------------------------------------- 1 | command('inspire')->hourly(); 16 | } 17 | 18 | /** 19 | * Register the commands for the application. 20 | */ 21 | protected function commands(): void 22 | { 23 | $this->load(__DIR__.'/Commands'); 24 | 25 | require base_path('routes/console.php'); 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /app/Providers/AuthServiceProvider.php: -------------------------------------------------------------------------------- 1 | 14 | */ 15 | protected $policies = [ 16 | // 'App\Models\Model' => 'App\Policies\ModelPolicy', 17 | ]; 18 | 19 | /** 20 | * Register any authentication / authorization services. 21 | */ 22 | public function boot(): void 23 | { 24 | // 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /resources/js/Components/SecondaryButton.vue: -------------------------------------------------------------------------------- 1 | 9 | 10 | 16 | -------------------------------------------------------------------------------- /resources/js/Components/MenuLabel.vue: -------------------------------------------------------------------------------- 1 | 7 | 8 | 24 | 25 | 26 | -------------------------------------------------------------------------------- /public/build/assets/Checkbox-97f2cde6.js: -------------------------------------------------------------------------------- 1 | import{z as d,j as l,l as u,u as n,o as p,d as k,H as i}from"./app-9cb26ff6.js";const m=["value"],h={__name:"Checkbox",props:{checked:{type:[Array,Boolean],default:!1},value:{default:null}},emits:["update:checked"],setup(r,{emit:s}){const o=r,e=d({get(){return o.checked},set(a){s("update:checked",a)}});return(a,t)=>l((p(),k("input",{type:"checkbox",value:r.value,"onUpdate:modelValue":t[0]||(t[0]=c=>i(e)?e.value=c:null),class:"rounded dark:bg-slate-900 border-slate-300 dark:border-slate-700 text-primary dark:text-primary shadow-sm focus:ring-primary/80 dark:focus:ring-primary dark:focus:ring-offset-slate-800 dark:checked:bg-primary dark:checked:border-primary"},null,8,m)),[[u,n(e)]])}};export{h as _}; 2 | -------------------------------------------------------------------------------- /resources/js/Components/PrimaryButton.vue: -------------------------------------------------------------------------------- 1 | 9 | 10 | 16 | -------------------------------------------------------------------------------- /app/Http/Middleware/Localization.php: -------------------------------------------------------------------------------- 1 | 2 | import SectionTitle from './SectionTitle.vue'; 3 | 4 | 5 | 23 | -------------------------------------------------------------------------------- /resources/stubs/Requests/Store.stub: -------------------------------------------------------------------------------- 1 | 21 | */ 22 | public function rules(): array 23 | { 24 | return [ 25 | 'name' => ['required', 'string', 'max:255'], 26 | ]; 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /resources/stubs/Requests/Update.stub: -------------------------------------------------------------------------------- 1 | 21 | */ 22 | public function rules(): array 23 | { 24 | return [ 25 | 'name' => ['required', 'string', 'max:255'], 26 | ]; 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /tests/Feature/ProfileInformationTest.php: -------------------------------------------------------------------------------- 1 | actingAs($user = User::factory()->create()); 16 | 17 | $response = $this->put('/user/profile-information', [ 18 | 'name' => 'Test Name', 19 | 'email' => 'test@example.com', 20 | ]); 21 | 22 | $this->assertEquals('Test Name', $user->fresh()->name); 23 | $this->assertEquals('test@example.com', $user->fresh()->email); 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /public/favicon.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 6 | -------------------------------------------------------------------------------- /database/seeders/DatabaseSeeder.php: -------------------------------------------------------------------------------- 1 | create(); 16 | 17 | // \App\Models\User::factory()->create([ 18 | // 'name' => 'Test User', 19 | // 'email' => 'test@example.com', 20 | // ]); 21 | $this->call([ 22 | PermissionSeeder::class, 23 | RoleSeeder::class, 24 | UserSeeder::class, 25 | SettingSeeder::class, 26 | ]); 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /app/Http/Middleware/TrustProxies.php: -------------------------------------------------------------------------------- 1 | |string|null 14 | */ 15 | protected $proxies; 16 | 17 | /** 18 | * The headers that should be used to detect proxies. 19 | * 20 | * @var int 21 | */ 22 | protected $headers = 23 | Request::HEADER_X_FORWARDED_FOR | 24 | Request::HEADER_X_FORWARDED_HOST | 25 | Request::HEADER_X_FORWARDED_PORT | 26 | Request::HEADER_X_FORWARDED_PROTO | 27 | Request::HEADER_X_FORWARDED_AWS_ELB; 28 | } 29 | -------------------------------------------------------------------------------- /app/Http/Requests/Permission/PermissionUpdateRequest.php: -------------------------------------------------------------------------------- 1 | 21 | */ 22 | public function rules(): array 23 | { 24 | return [ 25 | 'name' => 'required|max:255|unique:permissions,name,' . $this->permission->id, 26 | ]; 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /resources/js/Mixins/global.js: -------------------------------------------------------------------------------- 1 | // Mixins/global.js 2 | export default { 3 | // cache the value 4 | computed: { 5 | // langsung ambil seluruh page context 6 | page() { 7 | return this.$page; 8 | }, 9 | // shortcut ke shared auth 10 | auth() { 11 | return this.page.props.auth; 12 | }, 13 | }, 14 | 15 | // not cached and update every render 16 | methods: { 17 | can(permissions) { 18 | return permissions.some((perm) => this.$page.props.can?.[perm]); 19 | }, 20 | lang() { 21 | return this.$page.props.app.language.original; 22 | }, 23 | num(value) { 24 | return new Intl.NumberFormat("id-ID").format(value); 25 | }, 26 | }, 27 | }; 28 | -------------------------------------------------------------------------------- /app/Http/Requests/Permission/PermissionStoreRequest.php: -------------------------------------------------------------------------------- 1 | 22 | */ 23 | public function rules(): array 24 | { 25 | return [ 26 | 'name' => 'required|string|max:255|unique:' . Permission::class, 27 | ]; 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /resources/stubs/Migration.stub: -------------------------------------------------------------------------------- 1 | id(); 16 | $table->bigInteger('user_id')->unsigned(); 17 | $table->string('name'); 18 | $table->timestamps(); 19 | }); 20 | } 21 | 22 | /** 23 | * Reverse the migrations. 24 | */ 25 | public function down(): void 26 | { 27 | Schema::dropIfExists('{{modelNamePluralLowerCase}}'); 28 | } 29 | }; 30 | -------------------------------------------------------------------------------- /app/Http/Requests/Role/RoleUpdateRequest.php: -------------------------------------------------------------------------------- 1 | 21 | */ 22 | public function rules(): array 23 | { 24 | return [ 25 | 'name' => 'required|max:255|unique:roles,name,' . $this->role->id, 26 | 'permissions' => ['required', 'array'], 27 | ]; 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /database/migrations/2014_10_12_100000_create_password_reset_tokens_table.php: -------------------------------------------------------------------------------- 1 | string('email')->primary(); 16 | $table->string('token'); 17 | $table->timestamp('created_at')->nullable(); 18 | }); 19 | } 20 | 21 | /** 22 | * Reverse the migrations. 23 | */ 24 | public function down(): void 25 | { 26 | Schema::dropIfExists('password_reset_tokens'); 27 | } 28 | }; 29 | -------------------------------------------------------------------------------- /public/build/assets/Welcome-665689a9.css: -------------------------------------------------------------------------------- 1 | .bg-dots-darker{background-image:url("data:image/svg+xml,%3Csvg width='30' height='30' viewBox='0 0 30 30' fill='none' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath d='M1.22676 0C1.91374 0 2.45351 0.539773 2.45351 1.22676C2.45351 1.91374 1.91374 2.45351 1.22676 2.45351C0.539773 2.45351 0 1.91374 0 1.22676C0 0.539773 0.539773 0 1.22676 0Z' fill='rgba(0,0,0,0.07)'/%3E%3C/svg%3E")}@media (prefers-color-scheme: dark){.dark\:bg-dots-lighter{background-image:url("data:image/svg+xml,%3Csvg width='30' height='30' viewBox='0 0 30 30' fill='none' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath d='M1.22676 0C1.91374 0 2.45351 0.539773 2.45351 1.22676C2.45351 1.91374 1.91374 2.45351 1.22676 2.45351C0.539773 2.45351 0 1.91374 0 1.22676C0 0.539773 0.539773 0 1.22676 0Z' fill='rgba(255,255,255,0.07)'/%3E%3C/svg%3E")}} 2 | -------------------------------------------------------------------------------- /app/Http/Requests/Role/RoleStoreRequest.php: -------------------------------------------------------------------------------- 1 | 22 | */ 23 | public function rules(): array 24 | { 25 | return [ 26 | 'name' => 'required|string|max:255|unique:'.Role::class, 27 | 'permissions' => ['required', 'array'], 28 | ]; 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /app/Http/Requests/User/UserIndexRequest.php: -------------------------------------------------------------------------------- 1 | 21 | */ 22 | public function rules(): array 23 | { 24 | return [ 25 | 'field' => ['in:name,email,created_at,updated_at'], 26 | 'order' => ['in:asc,desc'], 27 | 'perPage' => ['numeric'], 28 | ]; 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /app/Http/Requests/Role/RoleIndexRequest.php: -------------------------------------------------------------------------------- 1 | 21 | */ 22 | public function rules(): array 23 | { 24 | return [ 25 | 'field' => ['in:name,guard_name,created_at,updated_at'], 26 | 'order' => ['in:asc,desc'], 27 | 'perPage' => ['numeric'], 28 | ]; 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /resources/stubs/Requests/Index.stub: -------------------------------------------------------------------------------- 1 | 21 | */ 22 | public function rules(): array 23 | { 24 | return [ 25 | 'field' => ['in:name,created_at,updated_at'], 26 | 'order' => ['in:asc,desc'], 27 | 'perPage' => ['numeric'], 28 | ]; 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /resources/js/Pages/API/Index.vue: -------------------------------------------------------------------------------- 1 | 11 | 12 | 26 | -------------------------------------------------------------------------------- /public/build/assets/PrivacyPolicy-147c9bb3.js: -------------------------------------------------------------------------------- 1 | import{o as l,d as o,b as t,u as i,X as n,a as e,F as r}from"./app-9cb26ff6.js";import{_ as c}from"./AuthenticationCardLogo-a2386df2.js";import"./ApplicationLogo-914092fa.js";import"./_plugin-vue_export-helper-c27b6911.js";const d={class:"font-sans text-slate-900 dark:text-slate-100 antialiased"},p={class:"pt-4 bg-slate-100 dark:bg-slate-900"},m={class:"min-h-screen flex flex-col items-center pt-6 sm:pt-0"},_=["innerHTML"],x={__name:"PrivacyPolicy",props:{policy:String},setup(s){return(a,u)=>(l(),o(r,null,[t(i(n),{title:a.lang().label.privacy_policy},null,8,["title"]),e("div",d,[e("div",p,[e("div",m,[e("div",null,[t(c)]),e("div",{class:"w-full sm:max-w-2xl mt-6 p-6 bg-white dark:bg-slate-800 shadow overflow-hidden sm:rounded prose dark:prose-invert",innerHTML:s.policy},null,8,_)])])])],64))}};export{x as default}; 2 | -------------------------------------------------------------------------------- /public/build/assets/TermsOfService-8f4b5ac0.js: -------------------------------------------------------------------------------- 1 | import{o as r,d as l,b as s,u as n,X as o,a as e,F as i}from"./app-9cb26ff6.js";import{_ as c}from"./AuthenticationCardLogo-a2386df2.js";import"./ApplicationLogo-914092fa.js";import"./_plugin-vue_export-helper-c27b6911.js";const d={class:"font-sans text-slate-900 dark:text-slate-100 antialiased"},m={class:"pt-4 bg-slate-100 dark:bg-slate-900"},_={class:"min-h-screen flex flex-col items-center pt-6 sm:pt-0"},p=["innerHTML"],x={__name:"TermsOfService",props:{terms:String},setup(t){return(a,f)=>(r(),l(i,null,[s(n(o),{title:a.lang().label.terms_of_service},null,8,["title"]),e("div",d,[e("div",m,[e("div",_,[e("div",null,[s(c)]),e("div",{class:"w-full sm:max-w-2xl mt-6 p-6 bg-white dark:bg-slate-800 shadow overflow-hidden sm:rounded prose dark:prose-invert",innerHTML:t.terms},null,8,p)])])])],64))}};export{x as default}; 2 | -------------------------------------------------------------------------------- /app/Actions/Fortify/ResetUserPassword.php: -------------------------------------------------------------------------------- 1 | $input 18 | */ 19 | public function reset(User $user, array $input): void 20 | { 21 | Validator::make($input, [ 22 | 'password' => $this->passwordRules(), 23 | ])->validate(); 24 | 25 | $user->forceFill([ 26 | 'password' => Hash::make($input['password']), 27 | ])->save(); 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /database/migrations/2023_08_21_033159_add_event_column_to_activity_log_table.php: -------------------------------------------------------------------------------- 1 | table(config('activitylog.table_name'), function (Blueprint $table) { 12 | $table->string('event')->nullable()->after('subject_type'); 13 | }); 14 | } 15 | 16 | public function down() 17 | { 18 | Schema::connection(config('activitylog.database_connection'))->table(config('activitylog.table_name'), function (Blueprint $table) { 19 | $table->dropColumn('event'); 20 | }); 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /app/Http/Requests/Permission/PermissionIndexRequest.php: -------------------------------------------------------------------------------- 1 | 21 | */ 22 | public function rules(): array 23 | { 24 | return [ 25 | 'field' => ['in:name,guard_name,created_at,updated_at'], 26 | 'order' => ['in:asc,desc'], 27 | 'perPage' => ['numeric'], 28 | ]; 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /database/migrations/2023_08_21_033200_add_batch_uuid_column_to_activity_log_table.php: -------------------------------------------------------------------------------- 1 | table(config('activitylog.table_name'), function (Blueprint $table) { 12 | $table->uuid('batch_uuid')->nullable()->after('properties'); 13 | }); 14 | } 15 | 16 | public function down() 17 | { 18 | Schema::connection(config('activitylog.database_connection'))->table(config('activitylog.table_name'), function (Blueprint $table) { 19 | $table->dropColumn('batch_uuid'); 20 | }); 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /resources/js/Layouts/Authenticated/Breadcrumb.vue: -------------------------------------------------------------------------------- 1 | 12 | 13 | 29 | -------------------------------------------------------------------------------- /app/Http/Requests/Activity/ActivityIndexRequest.php: -------------------------------------------------------------------------------- 1 | 21 | */ 22 | public function rules(): array 23 | { 24 | return [ 25 | 'field' => ['in:log_name,description,event,properties,created_at,updated_at'], 26 | 'order' => ['in:asc,desc'], 27 | 'perPage' => ['numeric'], 28 | ]; 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /resources/js/Components/BackToTop.vue: -------------------------------------------------------------------------------- 1 | 19 | 29 | -------------------------------------------------------------------------------- /resources/js/Components/SwitchDarkMode.vue: -------------------------------------------------------------------------------- 1 | 8 | 18 | -------------------------------------------------------------------------------- /resources/js/Components/Checkbox.vue: -------------------------------------------------------------------------------- 1 | 26 | 27 | 31 | -------------------------------------------------------------------------------- /public/build/assets/Properties-5d7d447e.js: -------------------------------------------------------------------------------- 1 | import{_ as u}from"./DialogModal-9a2b4a3d.js";import{_}from"./SecondaryButton-4d541c38.js";import{r as m,s as f,o as v,d as w,a as e,t as a,b as n,w as t,e as i}from"./app-9cb26ff6.js";import"./index-afa3307c.js";import"./Modal-d953ae84.js";const h={class:"space-y-2"},x={class:"rounded overflow-hidden"},B={__name:"Properties",props:{title:String,data:Object},setup(c){const o=c,s=m(!1),l=()=>{s.value=!1};return(d,r)=>{const p=f("json-viewer");return v(),w("div",null,[e("div",null,[e("p",{class:"text-primary underline cursor-pointer w-fit",onClick:r[0]||(r[0]=b=>s.value=!0)},a(o.title),1)]),n(u,{show:s.value,onClose:l,"max-width":"xl"},{title:t(()=>[i(a(o.title),1)]),content:t(()=>[e("div",h,[e("div",x,[n(p,{copyable:"",boxed:"",value:o.data},null,8,["value"])])])]),footer:t(()=>[n(_,{onClick:l},{default:t(()=>[i(a(d.lang().button.close),1)]),_:1})]),_:1},8,["show"])])}}};export{B as default}; 2 | -------------------------------------------------------------------------------- /database/seeders/SettingSeeder.php: -------------------------------------------------------------------------------- 1 | 1, 18 | 'favicon' => null, 19 | 'logo' => null, 20 | 'name' => 'Laravel Jarvis', 21 | 'short_name' => 'Jarvis', 22 | 'Description' => 'Laravel Jarvis is a starter project made with Laravel Jetstream Inertia Vue and add more additional feature like User, Role & Permission management, Responsive design, Light/Dark Mode, Rich Table with many features. Check the documentation for more installation.' 23 | ]); 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /public/build/assets/TextInput-f97bd710.js: -------------------------------------------------------------------------------- 1 | import{r as u,E as l,o as d,d as n,n as i}from"./app-9cb26ff6.js";const c=["value"],p={__name:"TextInput",props:{modelValue:String,error:{type:String,default:null}},emits:["update:modelValue"],setup(r,{expose:o}){const e=u(null);return l(()=>{e.value.hasAttribute("autofocus")&&e.value.focus()}),o({focus:()=>e.value.focus()}),(t,a)=>(d(),n("input",{ref_key:"input",ref:e,class:i([r.error?"border-rose-500 dark:border-rose-400 dark:bg-slate-900 dark:text-slate-300 focus:border-rose-500 dark:focus:border-rose-400 focus:ring-rose-500 dark:focus:ring-rose-400 ":"border-slate-300 dark:border-slate-700 dark:bg-slate-900 dark:text-slate-300 focus:border-primary dark:focus:border-primary focus:ring-primary dark:focus:ring-primary ","rounded shadow-sm placeholder:text-slate-300 dark:placeholder:text-slate-700"]),value:r.modelValue,onInput:a[0]||(a[0]=s=>t.$emit("update:modelValue",s.target.value))},null,42,c))}};export{p as _}; 2 | -------------------------------------------------------------------------------- /database/migrations/2019_08_19_000000_create_failed_jobs_table.php: -------------------------------------------------------------------------------- 1 | id(); 16 | $table->string('uuid')->unique(); 17 | $table->text('connection'); 18 | $table->text('queue'); 19 | $table->longText('payload'); 20 | $table->longText('exception'); 21 | $table->timestamp('failed_at')->useCurrent(); 22 | }); 23 | } 24 | 25 | /** 26 | * Reverse the migrations. 27 | */ 28 | public function down(): void 29 | { 30 | Schema::dropIfExists('failed_jobs'); 31 | } 32 | }; 33 | -------------------------------------------------------------------------------- /resources/js/Pages/PrivacyPolicy.vue: -------------------------------------------------------------------------------- 1 | 9 | 10 | 28 | -------------------------------------------------------------------------------- /database/migrations/2023_03_17_065919_create_sessions_table.php: -------------------------------------------------------------------------------- 1 | string('id')->primary(); 16 | $table->foreignId('user_id')->nullable()->index(); 17 | $table->string('ip_address', 45)->nullable(); 18 | $table->text('user_agent')->nullable(); 19 | $table->longText('payload'); 20 | $table->integer('last_activity')->index(); 21 | }); 22 | } 23 | 24 | /** 25 | * Reverse the migrations. 26 | */ 27 | public function down(): void 28 | { 29 | Schema::dropIfExists('sessions'); 30 | } 31 | }; 32 | -------------------------------------------------------------------------------- /resources/js/Pages/TermsOfService.vue: -------------------------------------------------------------------------------- 1 | 9 | 10 | 28 | -------------------------------------------------------------------------------- /public/build/assets/FormSection-fd684e6c.js: -------------------------------------------------------------------------------- 1 | import{z as n,S as l,o as i,d as a,b as c,w as r,A as t,a as e,p,n as u,u as m,g}from"./app-9cb26ff6.js";import{S as _}from"./SectionTitle-1f89132a.js";const b={class:"md:grid md:grid-cols-3 md:gap-6"},h={class:"mt-5 md:mt-0 md:col-span-2"},f={class:"grid grid-cols-6 gap-6"},v={key:0,class:"flex items-center justify-end px-4 py-3 bg-slate-50 dark:bg-slate-800 text-right sm:px-6 shadow sm:rounded-bl-md sm:rounded-br-md"},y={__name:"FormSection",emits:["submitted"],setup(S){const o=n(()=>!!l().actions);return(s,d)=>(i(),a("div",b,[c(_,null,{title:r(()=>[t(s.$slots,"title")]),description:r(()=>[t(s.$slots,"description")]),_:3}),e("div",h,[e("form",{onSubmit:d[0]||(d[0]=p(w=>s.$emit("submitted"),["prevent"]))},[e("div",{class:u(["px-4 py-5 bg-white dark:bg-slate-800 sm:p-6 shadow",m(o)?"sm:rounded-tl-md sm:rounded-tr-md":"sm:rounded"])},[e("div",f,[t(s.$slots,"form")])],2),m(o)?(i(),a("div",v,[t(s.$slots,"actions")])):g("",!0)],32)])]))}};export{y as _}; 2 | -------------------------------------------------------------------------------- /resources/js/Components/NavLink.vue: -------------------------------------------------------------------------------- 1 | 11 | 12 | 31 | -------------------------------------------------------------------------------- /resources/views/cli/pages.blade.php: -------------------------------------------------------------------------------- 1 | @props([ 2 | 'pageIndex' => '', 3 | 'pageForm' => '', 4 | ]) 5 | 6 |
7 |
8 | Jarvis Resource Generated 9 | 10 | @isset($pageIndex) 11 |
12 | {{ $pageIndex }} 13 | 14 | created 15 |
16 | @endisset 17 | 18 | @isset($pageForm) 19 |
20 | {{ $pageForm }} 21 | 22 | created 23 |
24 | @endisset 25 | 26 |
27 |
28 | -------------------------------------------------------------------------------- /config/cors.php: -------------------------------------------------------------------------------- 1 | ['api/*', 'sanctum/csrf-cookie'], 19 | 20 | 'allowed_methods' => ['*'], 21 | 22 | 'allowed_origins' => ['*'], 23 | 24 | 'allowed_origins_patterns' => [], 25 | 26 | 'allowed_headers' => ['*'], 27 | 28 | 'exposed_headers' => [], 29 | 30 | 'max_age' => 0, 31 | 32 | 'supports_credentials' => false, 33 | 34 | ]; 35 | -------------------------------------------------------------------------------- /app/Http/Requests/User/UserStoreRequest.php: -------------------------------------------------------------------------------- 1 | 23 | */ 24 | public function rules(): array 25 | { 26 | return [ 27 | 'name' => 'required|string|max:255', 28 | 'email' => 'required|string|email|max:255|unique:' . User::class, 29 | 'password' => ['required', 'confirmed', Password::defaults()], 30 | 'role' => ['required'], 31 | ]; 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /database/migrations/2023_03_30_111713_create_settings_table.php: -------------------------------------------------------------------------------- 1 | id(); 16 | $table->bigInteger('user_id')->unsigned(); 17 | $table->string('logo')->nullable(); 18 | $table->string('favicon')->nullable(); 19 | $table->string('name'); 20 | $table->string('short_name'); 21 | $table->text('description'); 22 | $table->timestamps(); 23 | }); 24 | } 25 | 26 | /** 27 | * Reverse the migrations. 28 | */ 29 | public function down(): void 30 | { 31 | Schema::dropIfExists('settings'); 32 | } 33 | }; 34 | -------------------------------------------------------------------------------- /resources/js/Components/Table.vue: -------------------------------------------------------------------------------- 1 | 2 | 3 | 29 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "private": true, 3 | "scripts": { 4 | "dev": "vite", 5 | "build": "vite build && vite build --ssr" 6 | }, 7 | "devDependencies": { 8 | "@inertiajs/vue3": "^2.0.5", 9 | "@tailwindcss/forms": "^0.5.2", 10 | "@tailwindcss/postcss": "^4.0.10", 11 | "@tailwindcss/typography": "^0.5.2", 12 | "@vitejs/plugin-vue": "^5.2.1", 13 | "@vue/server-renderer": "^3.5.13", 14 | "axios": "^1.1.2", 15 | "laravel-vite-plugin": "^1.2.0", 16 | "postcss": "^8.4.14", 17 | "tailwindcss": "^4.0.10", 18 | "vite": "^6.2.0", 19 | "vue": "^3.5.13" 20 | }, 21 | "dependencies": { 22 | "@headlessui/vue": "^1.7.12", 23 | "@heroicons/vue": "^2.0.16", 24 | "@vueuse/core": "^9.13.0", 25 | "floating-vue": "^2.0.0-beta.20", 26 | "lodash": "^4.17.21", 27 | "vue-json-viewer": "^3.0.4", 28 | "vue3-lottie": "^2.7.0", 29 | "ziggy-js": "^2.5.3" 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /resources/js/Components/SwitchLocale.vue: -------------------------------------------------------------------------------- 1 | 6 | -------------------------------------------------------------------------------- /tests/Feature/DeleteApiTokenTest.php: -------------------------------------------------------------------------------- 1 | markTestSkipped('API support is not enabled.'); 19 | 20 | return; 21 | } 22 | 23 | $this->actingAs($user = User::factory()->withPersonalTeam()->create()); 24 | 25 | $token = $user->tokens()->create([ 26 | 'name' => 'Test Token', 27 | 'token' => Str::random(40), 28 | 'abilities' => ['create', 'read'], 29 | ]); 30 | 31 | $response = $this->delete('/user/api-tokens/'.$token->id); 32 | 33 | $this->assertCount(0, $user->fresh()->tokens); 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /resources/js/Layouts/GuestLayout.vue: -------------------------------------------------------------------------------- 1 | 11 | 12 | 31 | -------------------------------------------------------------------------------- /resources/views/emails/team-invitation.blade.php: -------------------------------------------------------------------------------- 1 | @component('mail::message') 2 | {{ __('You have been invited to join the :team team!', ['team' => $invitation->team->name]) }} 3 | 4 | @if (Laravel\Fortify\Features::enabled(Laravel\Fortify\Features::registration())) 5 | {{ __('If you do not have an account, you may create one by clicking the button below. After creating an account, you may click the invitation acceptance button in this email to accept the team invitation:') }} 6 | 7 | @component('mail::button', ['url' => route('register')]) 8 | {{ __('Create Account') }} 9 | @endcomponent 10 | 11 | {{ __('If you already have an account, you may accept this invitation by clicking the button below:') }} 12 | 13 | @else 14 | {{ __('You may accept this invitation by clicking the button below:') }} 15 | @endif 16 | 17 | 18 | @component('mail::button', ['url' => $acceptUrl]) 19 | {{ __('Accept Invitation') }} 20 | @endcomponent 21 | 22 | {{ __('If you did not expect to receive an invitation to this team, you may discard this email.') }} 23 | @endcomponent 24 | -------------------------------------------------------------------------------- /database/migrations/2019_12_14_000001_create_personal_access_tokens_table.php: -------------------------------------------------------------------------------- 1 | id(); 16 | $table->morphs('tokenable'); 17 | $table->string('name'); 18 | $table->string('token', 64)->unique(); 19 | $table->text('abilities')->nullable(); 20 | $table->timestamp('last_used_at')->nullable(); 21 | $table->timestamp('expires_at')->nullable(); 22 | $table->timestamps(); 23 | }); 24 | } 25 | 26 | /** 27 | * Reverse the migrations. 28 | */ 29 | public function down(): void 30 | { 31 | Schema::dropIfExists('personal_access_tokens'); 32 | } 33 | }; 34 | -------------------------------------------------------------------------------- /public/build/assets/SwitchLocale-b9e142e8.js: -------------------------------------------------------------------------------- 1 | import{J as u,m as f,j as d,o as s,d as h,u as e,c as o,w as r,x as c,g as l,a as n}from"./app-9cb26ff6.js";const p={class:"inline-flex items-center justify-center rounded text-slate-400 dark:text-slate-500 hover:text-slate-500 dark:hover:text-slate-400 hover:bg-slate-100 dark:hover:bg-slate-900 focus:outline-none focus:bg-slate-100 dark:focus:bg-slate-900 focus:text-slate-500 dark:focus:text-slate-400 transition duration-150 ease-in-out"},_=n("p",{class:"m-2 text-center w-5 h-5"}," ID ",-1),x=n("p",{class:"m-2 text-center w-5 h-5"}," EN ",-1),g={__name:"SwitchLocale",setup(m){const a=u().props.app.locale;return(t,k)=>{const i=f("tooltip");return d((s(),h("span",p,[e(a)=="id"?(s(),o(e(c),{key:0,href:t.route("set-locale","en"),class:"flex justify-center items-center space-x-2"},{default:r(()=>[_]),_:1},8,["href"])):l("",!0),e(a)=="en"?(s(),o(e(c),{key:1,href:t.route("set-locale","id"),class:"flex justify-center items-center space-x-2"},{default:r(()=>[x]),_:1},8,["href"])):l("",!0)])),[[i,t.lang().label.set_locale]])}}};export{g as _}; 2 | -------------------------------------------------------------------------------- /app/Providers/EventServiceProvider.php: -------------------------------------------------------------------------------- 1 | > 16 | */ 17 | protected $listen = [ 18 | Registered::class => [ 19 | SendEmailVerificationNotification::class, 20 | ], 21 | ]; 22 | 23 | /** 24 | * Register any events for your application. 25 | */ 26 | public function boot(): void 27 | { 28 | // 29 | } 30 | 31 | /** 32 | * Determine if events and listeners should be automatically discovered. 33 | */ 34 | public function shouldDiscoverEvents(): bool 35 | { 36 | return false; 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /database/migrations/2014_10_12_000000_create_users_table.php: -------------------------------------------------------------------------------- 1 | id(); 16 | $table->string('name'); 17 | $table->string('email')->unique(); 18 | $table->timestamp('email_verified_at')->nullable(); 19 | $table->string('password'); 20 | $table->rememberToken(); 21 | $table->foreignId('current_team_id')->nullable(); 22 | $table->string('profile_photo_path', 2048)->nullable(); 23 | $table->timestamps(); 24 | }); 25 | } 26 | 27 | /** 28 | * Reverse the migrations. 29 | */ 30 | public function down(): void 31 | { 32 | Schema::dropIfExists('users'); 33 | } 34 | }; 35 | -------------------------------------------------------------------------------- /database/migrations/2023_08_21_033158_create_activity_log_table.php: -------------------------------------------------------------------------------- 1 | create(config('activitylog.table_name'), function (Blueprint $table) { 12 | $table->bigIncrements('id'); 13 | $table->string('log_name')->nullable(); 14 | $table->text('description'); 15 | $table->nullableMorphs('subject', 'subject'); 16 | $table->nullableMorphs('causer', 'causer'); 17 | $table->json('properties')->nullable(); 18 | $table->timestamps(); 19 | $table->index('log_name'); 20 | }); 21 | } 22 | 23 | public function down() 24 | { 25 | Schema::connection(config('activitylog.database_connection'))->dropIfExists(config('activitylog.table_name')); 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /app/Actions/Fortify/UpdateUserPassword.php: -------------------------------------------------------------------------------- 1 | $input 18 | */ 19 | public function update(User $user, array $input): void 20 | { 21 | Validator::make($input, [ 22 | 'current_password' => ['required', 'string', 'current_password:web'], 23 | 'password' => $this->passwordRules(), 24 | ], [ 25 | 'current_password.current_password' => __('The provided password does not match your current password.'), 26 | ])->validateWithBag('updatePassword'); 27 | 28 | $user->forceFill([ 29 | 'password' => Hash::make($input['password']), 30 | ])->save(); 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /app/Providers/JetstreamServiceProvider.php: -------------------------------------------------------------------------------- 1 | configurePermissions(); 25 | 26 | Jetstream::deleteUsersUsing(DeleteUser::class); 27 | } 28 | 29 | /** 30 | * Configure the permissions that are available within the application. 31 | */ 32 | protected function configurePermissions(): void 33 | { 34 | Jetstream::defaultApiTokenPermissions(['read']); 35 | 36 | Jetstream::permissions([ 37 | 'create', 38 | 'read', 39 | 'update', 40 | 'delete', 41 | ]); 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /public/build/assets/SelectInput-188689a8.js: -------------------------------------------------------------------------------- 1 | import{r as n,E as i,o as t,d as o,F as c,f,t as m,n as p}from"./app-9cb26ff6.js";const k=["value"],b=["value"],v={__name:"SelectInput",props:{modelValue:Number|String,dataSet:{type:Object,default:[]},error:{type:String,default:null}},emits:["update:modelValue"],setup(a,{expose:l}){const e=n(null);return i(()=>{e.value.hasAttribute("autofocus")&&e.value.focus()}),l({focus:()=>e.value.focus()}),(u,s)=>(t(),o("select",{ref_key:"input",ref:e,class:p([a.error?"border-rose-500 dark:border-rose-400 dark:bg-slate-900 dark:text-slate-300 focus:border-rose-500 dark:focus:border-rose-400 focus:ring-rose-500 dark:focus:ring-rose-400 ":"border-slate-300 dark:border-slate-700 dark:bg-slate-900 dark:text-slate-300 focus:border-primary dark:focus:border-primary focus:ring-primary dark:focus:ring-primary ","rounded shadow-sm placeholder:text-slate-300 dark:placeholder:text-slate-700"]),value:a.modelValue,onInput:s[0]||(s[0]=r=>u.$emit("update:modelValue",r.target.value))},[(t(!0),o(c,null,f(a.dataSet,(r,d)=>(t(),o("option",{key:d,value:r.value},m(r.label),9,b))),128))],42,k))}};export{v as _}; 2 | -------------------------------------------------------------------------------- /app/Http/Middleware/RedirectIfAuthenticated.php: -------------------------------------------------------------------------------- 1 | check()) { 25 | Session::flash('flash.text', 'Welcome back! '.auth()->user()->name); 26 | Session::flash('flash.style', 'info'); 27 | return redirect(RouteServiceProvider::HOME); 28 | } 29 | } 30 | 31 | return $next($request); 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /app/Http/Requests/User/UserUpdateRequest.php: -------------------------------------------------------------------------------- 1 | 22 | */ 23 | public function rules(): array 24 | { 25 | return [ 26 | 'name' => ['required', 'string', 'max:255'], 27 | 'email' => 'required|unique:users,email,' . $this->user->id, 28 | 'password' => ['nullable', 'confirmed', Password::defaults()], 29 | 'password_confirmation' => 'sometimes|required_with:password|same:password', 30 | 'role' => ['required'], 31 | ]; 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /config/services.php: -------------------------------------------------------------------------------- 1 | [ 18 | 'domain' => env('MAILGUN_DOMAIN'), 19 | 'secret' => env('MAILGUN_SECRET'), 20 | 'endpoint' => env('MAILGUN_ENDPOINT', 'api.mailgun.net'), 21 | 'scheme' => 'https', 22 | ], 23 | 24 | 'postmark' => [ 25 | 'token' => env('POSTMARK_TOKEN'), 26 | ], 27 | 28 | 'ses' => [ 29 | 'key' => env('AWS_ACCESS_KEY_ID'), 30 | 'secret' => env('AWS_SECRET_ACCESS_KEY'), 31 | 'region' => env('AWS_DEFAULT_REGION', 'us-east-1'), 32 | ], 33 | 34 | ]; 35 | -------------------------------------------------------------------------------- /resources/js/ssr.js: -------------------------------------------------------------------------------- 1 | import { createSSRApp, h } from 'vue'; 2 | import { renderToString } from '@vue/server-renderer'; 3 | import { createInertiaApp } from '@inertiajs/vue3'; 4 | import createServer from '@inertiajs/vue3/server'; 5 | import { resolvePageComponent } from 'laravel-vite-plugin/inertia-helpers'; 6 | import { ZiggyVue } from '../../vendor/tightenco/ziggy/dist/vue.m'; 7 | import JsonViewer from 'vue-json-viewer/ssr' 8 | 9 | const appName = 'Laravel'; 10 | 11 | createServer((page) => 12 | createInertiaApp({ 13 | page, 14 | render: renderToString, 15 | title: (title) => `${title} - ${appName}`, 16 | resolve: (name) => resolvePageComponent(`./Pages/${name}.vue`, import.meta.glob('./Pages/**/*.vue')), 17 | setup({ App, props, plugin }) { 18 | return createSSRApp({ render: () => h(App, props) }) 19 | .use(plugin) 20 | .use(JsonViewer) 21 | .use(ZiggyVue, { 22 | ...page.props.ziggy, 23 | location: new URL(page.props.ziggy.location), 24 | }); 25 | }, 26 | }) 27 | ); 28 | -------------------------------------------------------------------------------- /resources/js/Components/TextInput.vue: -------------------------------------------------------------------------------- 1 | 24 | 25 | 31 | -------------------------------------------------------------------------------- /routes/api.php: -------------------------------------------------------------------------------- 1 | ['auth:sanctum']], function () { 23 | Route::get('/user', function (Request $request) { 24 | return $request->user(); 25 | }); 26 | Route::post('/logout', [AuthController::class, 'logout']); 27 | Route::get('/profile', [UserController::class, 'profile']); 28 | Route::put('/update-password', [UserController::class, 'updatePassword']); 29 | }); 30 | -------------------------------------------------------------------------------- /resources/js/Components/TextAreaInput.vue: -------------------------------------------------------------------------------- 1 | 24 | 25 | 31 | -------------------------------------------------------------------------------- /resources/js/Components/Offline.vue: -------------------------------------------------------------------------------- 1 | 10 | 29 | -------------------------------------------------------------------------------- /tests/Feature/CreateApiTokenTest.php: -------------------------------------------------------------------------------- 1 | markTestSkipped('API support is not enabled.'); 18 | 19 | return; 20 | } 21 | 22 | $this->actingAs($user = User::factory()->withPersonalTeam()->create()); 23 | 24 | $response = $this->post('/user/api-tokens', [ 25 | 'name' => 'Test Token', 26 | 'permissions' => [ 27 | 'read', 28 | 'update', 29 | ], 30 | ]); 31 | 32 | $this->assertCount(1, $user->fresh()->tokens); 33 | $this->assertEquals('Test Token', $user->fresh()->tokens->first()->name); 34 | $this->assertTrue($user->fresh()->tokens->first()->can('read')); 35 | $this->assertFalse($user->fresh()->tokens->first()->can('delete')); 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /config/view.php: -------------------------------------------------------------------------------- 1 | [ 17 | resource_path('views'), 18 | ], 19 | 20 | /* 21 | |-------------------------------------------------------------------------- 22 | | Compiled View Path 23 | |-------------------------------------------------------------------------- 24 | | 25 | | This option determines where all the compiled Blade templates will be 26 | | stored for your application. Typically, this is within the storage 27 | | directory. However, as usual, you are free to change this value. 28 | | 29 | */ 30 | 31 | 'compiled' => env( 32 | 'VIEW_COMPILED_PATH', 33 | realpath(storage_path('framework/views')) 34 | ), 35 | 36 | ]; 37 | -------------------------------------------------------------------------------- /resources/js/Layouts/Authenticated/Footer.vue: -------------------------------------------------------------------------------- 1 | 2 | 30 | -------------------------------------------------------------------------------- /resources/js/Layouts/Guest/Footer.vue: -------------------------------------------------------------------------------- 1 | 2 | 30 | -------------------------------------------------------------------------------- /resources/js/Components/DropdownLink.vue: -------------------------------------------------------------------------------- 1 | 9 | 10 | 27 | -------------------------------------------------------------------------------- /app/Actions/Fortify/CreateNewUser.php: -------------------------------------------------------------------------------- 1 | $input 19 | */ 20 | public function create(array $input): User 21 | { 22 | Validator::make($input, [ 23 | 'name' => ['required', 'string', 'max:255'], 24 | 'email' => ['required', 'string', 'email', 'max:255', 'unique:users'], 25 | 'password' => $this->passwordRules(), 26 | 'terms' => Jetstream::hasTermsAndPrivacyPolicyFeature() ? ['accepted', 'required'] : '', 27 | ])->validate(); 28 | 29 | $user = User::create([ 30 | 'name' => $input['name'], 31 | 'email' => $input['email'], 32 | 'password' => Hash::make($input['password']), 33 | ]); 34 | $user->assignRole('operator'); 35 | return $user; 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /app/Exceptions/Handler.php: -------------------------------------------------------------------------------- 1 | , \Psr\Log\LogLevel::*> 14 | */ 15 | protected $levels = [ 16 | // 17 | ]; 18 | 19 | /** 20 | * A list of the exception types that are not reported. 21 | * 22 | * @var array> 23 | */ 24 | protected $dontReport = [ 25 | // 26 | ]; 27 | 28 | /** 29 | * A list of the inputs that are never flashed to the session on validation exceptions. 30 | * 31 | * @var array 32 | */ 33 | protected $dontFlash = [ 34 | 'current_password', 35 | 'password', 36 | 'password_confirmation', 37 | ]; 38 | 39 | /** 40 | * Register the exception handling callbacks for the application. 41 | */ 42 | public function register(): void 43 | { 44 | $this->reportable(function (Throwable $e) { 45 | // 46 | }); 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /tests/Feature/AuthenticationTest.php: -------------------------------------------------------------------------------- 1 | get('/login'); 17 | 18 | $response->assertStatus(200); 19 | } 20 | 21 | public function test_users_can_authenticate_using_the_login_screen(): void 22 | { 23 | $user = User::factory()->create(); 24 | 25 | $response = $this->post('/login', [ 26 | 'email' => $user->email, 27 | 'password' => 'password', 28 | ]); 29 | 30 | $this->assertAuthenticated(); 31 | $response->assertRedirect(RouteServiceProvider::HOME); 32 | } 33 | 34 | public function test_users_can_not_authenticate_with_invalid_password(): void 35 | { 36 | $user = User::factory()->create(); 37 | 38 | $this->post('/login', [ 39 | 'email' => $user->email, 40 | 'password' => 'wrong-password', 41 | ]); 42 | 43 | $this->assertGuest(); 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /vite.config.js: -------------------------------------------------------------------------------- 1 | import { defineConfig, loadEnv } from "vite"; 2 | import laravel from "laravel-vite-plugin"; 3 | import vue from "@vitejs/plugin-vue"; 4 | import path from "path"; 5 | 6 | export default defineConfig({ 7 | plugins: [ 8 | laravel({ 9 | input: "resources/js/app.js", 10 | ssr: "resources/js/ssr.js", 11 | refresh: true, 12 | }), 13 | vue({ 14 | template: { 15 | transformAssetUrls: { 16 | base: null, 17 | includeAbsolute: false, 18 | }, 19 | }, 20 | }), 21 | ], 22 | resolve: { 23 | alias: { 24 | "@": path.resolve(__dirname, "resources/js"), 25 | }, 26 | }, 27 | 28 | /** 29 | * This line is for prevent CORS error when developing environment 30 | * Change with your local development domain 31 | * In this case, I'm using http://jarvis.test provided by Laragon 32 | * If you're using Valet, you can use http://jarvis.test 33 | * Or if you're using XAMPP, you can use http://localhost:your-port (default is 80) 34 | */ 35 | server: { 36 | cors: { 37 | origin: ["http://laravel-jarvis.test"], 38 | }, 39 | }, 40 | }); 41 | -------------------------------------------------------------------------------- /.env.example: -------------------------------------------------------------------------------- 1 | APP_NAME="Jarvis" 2 | APP_ENV=local 3 | APP_KEY= 4 | APP_DEBUG=true 5 | APP_URL=http://jarvis.test 6 | APP_VERSION=0.9 7 | 8 | LOG_CHANNEL=stack 9 | LOG_DEPRECATIONS_CHANNEL=null 10 | LOG_LEVEL=debug 11 | 12 | DB_CONNECTION=mysql 13 | DB_HOST=127.0.0.1 14 | DB_PORT=3306 15 | DB_DATABASE=jarvis_dev 16 | DB_USERNAME=root 17 | DB_PASSWORD= 18 | 19 | BROADCAST_DRIVER=log 20 | CACHE_DRIVER=file 21 | FILESYSTEM_DISK=local 22 | QUEUE_CONNECTION=sync 23 | SESSION_DRIVER=database 24 | SESSION_LIFETIME=120 25 | 26 | MEMCACHED_HOST=127.0.0.1 27 | 28 | REDIS_HOST=127.0.0.1 29 | REDIS_PASSWORD=null 30 | REDIS_PORT=6379 31 | 32 | MAIL_MAILER=smtp 33 | MAIL_HOST=mailpit 34 | MAIL_PORT=1025 35 | MAIL_USERNAME=null 36 | MAIL_PASSWORD=null 37 | MAIL_ENCRYPTION=null 38 | MAIL_FROM_ADDRESS="hello@example.com" 39 | MAIL_FROM_NAME="${APP_NAME}" 40 | 41 | AWS_ACCESS_KEY_ID= 42 | AWS_SECRET_ACCESS_KEY= 43 | AWS_DEFAULT_REGION=us-east-1 44 | AWS_BUCKET= 45 | AWS_USE_PATH_STYLE_ENDPOINT=false 46 | 47 | PUSHER_APP_ID= 48 | PUSHER_APP_KEY= 49 | PUSHER_APP_SECRET= 50 | PUSHER_HOST= 51 | PUSHER_PORT=443 52 | PUSHER_SCHEME=https 53 | PUSHER_APP_CLUSTER=mt1 54 | 55 | VITE_PUSHER_APP_KEY="${PUSHER_APP_KEY}" 56 | VITE_PUSHER_HOST="${PUSHER_HOST}" 57 | VITE_PUSHER_PORT="${PUSHER_PORT}" 58 | VITE_PUSHER_SCHEME="${PUSHER_SCHEME}" 59 | VITE_PUSHER_APP_CLUSTER="${PUSHER_APP_CLUSTER}" 60 | -------------------------------------------------------------------------------- /tests/Feature/DeleteAccountTest.php: -------------------------------------------------------------------------------- 1 | markTestSkipped('Account deletion is not enabled.'); 18 | 19 | return; 20 | } 21 | 22 | $this->actingAs($user = User::factory()->create()); 23 | 24 | $response = $this->delete('/user', [ 25 | 'password' => 'password', 26 | ]); 27 | 28 | $this->assertNull($user->fresh()); 29 | } 30 | 31 | public function test_correct_password_must_be_provided_before_account_can_be_deleted(): void 32 | { 33 | if (! Features::hasAccountDeletionFeatures()) { 34 | $this->markTestSkipped('Account deletion is not enabled.'); 35 | 36 | return; 37 | } 38 | 39 | $this->actingAs($user = User::factory()->create()); 40 | 41 | $response = $this->delete('/user', [ 42 | 'password' => 'wrong-password', 43 | ]); 44 | 45 | $this->assertNotNull($user->fresh()); 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /tests/Feature/PasswordConfirmationTest.php: -------------------------------------------------------------------------------- 1 | withPersonalTeam()->create(); 17 | 18 | $response = $this->actingAs($user)->get('/user/confirm-password'); 19 | 20 | $response->assertStatus(200); 21 | } 22 | 23 | public function test_password_can_be_confirmed(): void 24 | { 25 | $user = User::factory()->create(); 26 | 27 | $response = $this->actingAs($user)->post('/user/confirm-password', [ 28 | 'password' => 'password', 29 | ]); 30 | 31 | $response->assertRedirect(); 32 | $response->assertSessionHasNoErrors(); 33 | } 34 | 35 | public function test_password_is_not_confirmed_with_invalid_password(): void 36 | { 37 | $user = User::factory()->create(); 38 | 39 | $response = $this->actingAs($user)->post('/user/confirm-password', [ 40 | 'password' => 'wrong-password', 41 | ]); 42 | 43 | $response->assertSessionHasErrors(); 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /database/seeders/UserSeeder.php: -------------------------------------------------------------------------------- 1 | 'Superadmin', 18 | 'email' => 'superadmin@superadmin.com', 19 | 'password' => bcrypt('superadmin'), 20 | 'email_verified_at' => date('Y-m-d H:i') 21 | ]); 22 | $superadmin->assignRole('superadmin'); 23 | 24 | $admin = User::create([ 25 | 'name' => 'Admin', 26 | 'email' => 'admin@admin.com', 27 | 'password' => bcrypt('admin'), 28 | 'email_verified_at' => date('Y-m-d H:i') 29 | ]); 30 | $admin->assignRole('admin'); 31 | 32 | $operator = User::create([ 33 | 'name' => 'Operator', 34 | 'email' => 'operator@operator.com', 35 | 'password' => bcrypt('operator'), 36 | 'email_verified_at' => date('Y-m-d H:i') 37 | ]); 38 | $operator->assignRole('operator'); 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /resources/js/Components/SelectInput.vue: -------------------------------------------------------------------------------- 1 | 28 | 29 | 37 | -------------------------------------------------------------------------------- /resources/js/Components/ResponsiveNavLink.vue: -------------------------------------------------------------------------------- 1 | 17 | 18 | 29 | -------------------------------------------------------------------------------- /resources/stubs/Model.stub: -------------------------------------------------------------------------------- 1 | logFillable()->setDescriptionForEvent(fn(string $eventName) => auth()->user()?->name." {$eventName} ".$this->getTable()); 24 | } 25 | 26 | public function getCreatedAtAttribute() 27 | { 28 | return Carbon::parse($this->attributes['created_at'])->isoFormat('D MMMM Y HH:mm'); 29 | } 30 | 31 | public function getUpdatedAtAttribute() 32 | { 33 | return Carbon::parse($this->attributes['updated_at'])->isoFormat('D MMMM Y HH:mm'); 34 | } 35 | 36 | /** 37 | * Get the user that owns the Setting 38 | * 39 | * @return \Illuminate\Database\Eloquent\Relations\BelongsTo 40 | */ 41 | public function user(): BelongsTo 42 | { 43 | return $this->belongsTo(User::class); 44 | } 45 | } -------------------------------------------------------------------------------- /resources/js/bootstrap.js: -------------------------------------------------------------------------------- 1 | /** 2 | * We'll load the axios HTTP library which allows us to easily issue requests 3 | * to our Laravel back-end. This library automatically handles sending the 4 | * CSRF token as a header based on the value of the "XSRF" token cookie. 5 | */ 6 | 7 | import axios from 'axios'; 8 | window.axios = axios; 9 | 10 | window.axios.defaults.headers.common['X-Requested-With'] = 'XMLHttpRequest'; 11 | 12 | /** 13 | * Echo exposes an expressive API for subscribing to channels and listening 14 | * for events that are broadcast by Laravel. Echo and event broadcasting 15 | * allows your team to easily build robust real-time web applications. 16 | */ 17 | 18 | // import Echo from 'laravel-echo'; 19 | 20 | // import Pusher from 'pusher-js'; 21 | // window.Pusher = Pusher; 22 | 23 | // window.Echo = new Echo({ 24 | // broadcaster: 'pusher', 25 | // key: import.meta.env.VITE_PUSHER_APP_KEY, 26 | // cluster: import.meta.env.VITE_PUSHER_APP_CLUSTER ?? 'mt1', 27 | // wsHost: import.meta.env.VITE_PUSHER_HOST ? import.meta.env.VITE_PUSHER_HOST : `ws-${import.meta.env.VITE_PUSHER_APP_CLUSTER}.pusher.com`, 28 | // wsPort: import.meta.env.VITE_PUSHER_PORT ?? 80, 29 | // wssPort: import.meta.env.VITE_PUSHER_PORT ?? 443, 30 | // forceTLS: (import.meta.env.VITE_PUSHER_SCHEME ?? 'https') === 'https', 31 | // enabledTransports: ['ws', 'wss'], 32 | // }); 33 | -------------------------------------------------------------------------------- /public/build/assets/DialogModal-9a2b4a3d.js: -------------------------------------------------------------------------------- 1 | import{o as i,c as r,w as n,a as e,A as a,p as d,b as c,u as f}from"./app-9cb26ff6.js";import{X as x}from"./index-afa3307c.js";import{_ as m}from"./Modal-d953ae84.js";const u={class:"px-6 py-4"},h={class:"flex justify-between items-center text-lg font-medium text-slate-900 dark:text-slate-100 space-x-2"},b=["onClick"],p={class:"mt-4 text-sm text-slate-600 dark:text-slate-400"},k={class:"flex flex-row justify-end px-6 py-4 bg-slate-100 dark:bg-slate-900/30 text-right"},y={__name:"DialogModal",props:{show:{type:Boolean,default:!1},maxWidth:{type:String,default:"xl"},closeable:{type:Boolean,default:!1}},emits:["close"],setup(t,{emit:l}){const o=()=>{l("close")};return(s,_)=>(i(),r(m,{show:t.show,"max-width":t.maxWidth,closeable:t.closeable,onClose:o},{default:n(()=>[e("div",u,[e("div",h,[a(s.$slots,"title"),e("button",{class:"inline-flex items-center p-2 border border-slate-200 dark:border-slate-700 text-sm leading-4 font-medium rounded text-slate-500 dark:text-slate-400 bg-white dark:bg-slate-800 hover:text-slate-700 dark:hover:text-slate-300 focus:outline-none focus:bg-slate-50 dark:focus:bg-slate-700 active:bg-slate-50 dark:active:bg-slate-700 transition ease-in-out duration-150",onClick:d(o,["prevent"])},[c(f(x),{class:"w-4 h-auto"})],8,b)]),e("div",p,[a(s.$slots,"content")])]),e("div",k,[a(s.$slots,"footer")])]),_:3},8,["show","max-width","closeable"]))}};export{y as _}; 2 | -------------------------------------------------------------------------------- /tests/Feature/ApiTokenPermissionsTest.php: -------------------------------------------------------------------------------- 1 | markTestSkipped('API support is not enabled.'); 19 | 20 | return; 21 | } 22 | 23 | $this->actingAs($user = User::factory()->withPersonalTeam()->create()); 24 | 25 | $token = $user->tokens()->create([ 26 | 'name' => 'Test Token', 27 | 'token' => Str::random(40), 28 | 'abilities' => ['create', 'read'], 29 | ]); 30 | 31 | $response = $this->put('/user/api-tokens/'.$token->id, [ 32 | 'name' => $token->name, 33 | 'permissions' => [ 34 | 'delete', 35 | 'missing-permission', 36 | ], 37 | ]); 38 | 39 | $this->assertTrue($user->fresh()->tokens->first()->can('delete')); 40 | $this->assertFalse($user->fresh()->tokens->first()->can('read')); 41 | $this->assertFalse($user->fresh()->tokens->first()->can('missing-permission')); 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /public/build/assets/Index-67a1ca5c.js: -------------------------------------------------------------------------------- 1 | import s from"./ApiTokenManager-bf9fcdb3.js";import{_ as r}from"./AppLayout-706b8ec5.js";import{o as e,c as m,w as a,a as t,t as p,b as l}from"./app-9cb26ff6.js";import"./ActionMessage-dafe95ee.js";import"./ActionSection-bb60ff24.js";import"./SectionTitle-1f89132a.js";import"./_plugin-vue_export-helper-c27b6911.js";import"./Checkbox-97f2cde6.js";import"./ConfirmationModal-8097dc2a.js";import"./Modal-d953ae84.js";import"./DangerButton-db69db22.js";import"./DialogModal-9a2b4a3d.js";import"./index-afa3307c.js";import"./FormSection-fd684e6c.js";import"./InputError-71f77be1.js";import"./InputLabel-ef1a5601.js";import"./PrimaryButton-0fb1f193.js";import"./SecondaryButton-4d541c38.js";import"./SectionBorder-6e176b61.js";import"./TextInput-f97bd710.js";import"./Toast-48e493b9.js";import"./SwitchDarkMode-e6f628a6.js";import"./SwitchLocale-b9e142e8.js";import"./ApplicationLogo-914092fa.js";const n={class:"max-w-7xl mx-auto py-10 sm:px-6 lg:px-8"},z={__name:"Index",props:{tokens:Array,availablePermissions:Array,defaultPermissions:Array},setup(i){return(o,c)=>(e(),m(r,{title:o.lang().label.api_tokens},{title:a(()=>[t("span",null,p(o.lang().label.api_tokens),1)]),default:a(()=>[t("div",null,[t("div",n,[l(s,{tokens:i.tokens,"available-permissions":i.availablePermissions,"default-permissions":i.defaultPermissions},null,8,["tokens","available-permissions","default-permissions"])])])]),_:1},8,["title"]))}};export{z as default}; 2 | -------------------------------------------------------------------------------- /public/build/assets/Delete-79d257e4.js: -------------------------------------------------------------------------------- 1 | import{r as g,v as b,m as h,o as d,d as w,j as k,c as C,w as t,b as l,u as a,p as $,e as r,t as o,n as D}from"./app-9cb26ff6.js";import{_ as S}from"./ConfirmationModal-8097dc2a.js";import{_ as y}from"./ActionButton-0ff23640.js";import{_ as B}from"./DangerButton-db69db22.js";import{_ as T}from"./SecondaryButton-4d541c38.js";import{T as j}from"./index-afa3307c.js";import"./Modal-d953ae84.js";const O={__name:"Delete",props:{title:String,role:Object},emits:["open"],setup(f,{emit:u}){const n=f,i=g(!1),s=b({}),_=()=>{var e;s.delete(route("role.destroy",(e=n.role)==null?void 0:e.id),{preserveScroll:!0,onSuccess:()=>c(),onError:()=>null,onFinish:()=>null})},c=()=>{i.value=!1};return(e,p)=>{const v=h("tooltip");return d(),w("div",null,[k((d(),C(y,{variant:"danger",onClick:p[0]||(p[0]=$(m=>(i.value=!0,u("open")),["prevent"]))},{default:t(()=>[l(a(j),{class:"w-4 h-auto"})]),_:1})),[[v,e.lang().label.delete]]),l(S,{show:i.value,onClose:c},{title:t(()=>[r(o(e.lang().label.delete)+" "+o(n.title),1)]),content:t(()=>{var m;return[r(o(e.lang().label.delete_confirm)+" "+o((m=n.role)==null?void 0:m.name)+"? ",1)]}),footer:t(()=>[l(T,{onClick:c},{default:t(()=>[r(o(e.lang().button.cancel),1)]),_:1}),l(B,{class:D(["ml-3",{"opacity-25":a(s).processing}]),disabled:a(s).processing,onClick:_},{default:t(()=>[r(o(e.lang().button.delete)+" "+o(a(s).processing?"...":""),1)]),_:1},8,["class","disabled"])]),_:1},8,["show"])])}}};export{O as default}; 2 | -------------------------------------------------------------------------------- /public/build/assets/Delete-a6494205.js: -------------------------------------------------------------------------------- 1 | import{r as g,v as b,m as h,o as p,d as w,j as k,c as C,w as o,b as a,u as l,p as $,e as r,t as s,n as D}from"./app-9cb26ff6.js";import{_ as S}from"./ConfirmationModal-8097dc2a.js";import{_ as y}from"./ActionButton-0ff23640.js";import{_ as B}from"./DangerButton-db69db22.js";import{_ as T}from"./SecondaryButton-4d541c38.js";import{T as j}from"./index-afa3307c.js";import"./Modal-d953ae84.js";const O={__name:"Delete",props:{title:String,user:Object},emits:["open"],setup(d,{emit:f}){const n=d,i=g(!1),t=b({}),_=()=>{var e;t.delete(route("user.destroy",(e=n.user)==null?void 0:e.id),{preserveScroll:!0,onSuccess:()=>c(),onError:()=>null,onFinish:()=>null})},c=()=>{i.value=!1};return(e,u)=>{const v=h("tooltip");return p(),w("div",null,[k((p(),C(y,{variant:"danger",onClick:u[0]||(u[0]=$(m=>(i.value=!0,f("open")),["prevent"]))},{default:o(()=>[a(l(j),{class:"w-4 h-auto"})]),_:1})),[[v,e.lang().label.delete]]),a(S,{show:i.value,onClose:c},{title:o(()=>[r(s(e.lang().label.delete)+" "+s(n.title),1)]),content:o(()=>{var m;return[r(s(e.lang().label.delete_confirm)+" "+s((m=n.user)==null?void 0:m.name)+"? ",1)]}),footer:o(()=>[a(T,{onClick:c},{default:o(()=>[r(s(e.lang().button.cancel),1)]),_:1}),a(B,{class:D(["ml-3",{"opacity-25":l(t).processing}]),disabled:l(t).processing,onClick:_},{default:o(()=>[r(s(e.lang().button.delete)+" "+s(l(t).processing?"...":""),1)]),_:1},8,["class","disabled"])]),_:1},8,["show"])])}}};export{O as default}; 2 | -------------------------------------------------------------------------------- /resources/js/Components/FormSection.vue: -------------------------------------------------------------------------------- 1 | 9 | 10 | 38 | -------------------------------------------------------------------------------- /public/build/assets/Delete-787e6cee.js: -------------------------------------------------------------------------------- 1 | import{r as g,v as b,m as h,o as d,d as w,j as y,c as k,w as o,b as a,u as l,p as C,e as i,t,n as $}from"./app-9cb26ff6.js";import{_ as D}from"./ConfirmationModal-8097dc2a.js";import{_ as S}from"./ActionButton-0ff23640.js";import{_ as B}from"./DangerButton-db69db22.js";import{_ as T}from"./SecondaryButton-4d541c38.js";import{T as j}from"./index-afa3307c.js";import"./Modal-d953ae84.js";const O={__name:"Delete",props:{title:String,activity:Object},emits:["open"],setup(f,{emit:u}){const r=f,n=g(!1),s=b({}),_=()=>{var e;s.delete(route("activity.destroy",(e=r.activity)==null?void 0:e.id),{preserveScroll:!0,onSuccess:()=>c(),onError:()=>null,onFinish:()=>null})},c=()=>{n.value=!1};return(e,p)=>{const v=h("tooltip");return d(),w("div",null,[y((d(),k(S,{variant:"danger",onClick:p[0]||(p[0]=C(m=>(n.value=!0,u("open")),["prevent"]))},{default:o(()=>[a(l(j),{class:"w-4 h-auto"})]),_:1})),[[v,e.lang().label.delete]]),a(D,{show:n.value,onClose:c},{title:o(()=>[i(t(e.lang().label.delete)+" "+t(r.title),1)]),content:o(()=>{var m;return[i(t(e.lang().label.delete_confirm)+" "+t((m=r.activity)==null?void 0:m.description)+"? ",1)]}),footer:o(()=>[a(T,{onClick:c},{default:o(()=>[i(t(e.lang().button.cancel),1)]),_:1}),a(B,{class:$(["ml-3",{"opacity-25":l(s).processing}]),disabled:l(s).processing,onClick:_},{default:o(()=>[i(t(e.lang().button.delete)+" "+t(l(s).processing?"...":""),1)]),_:1},8,["class","disabled"])]),_:1},8,["show"])])}}};export{O as default}; 2 | -------------------------------------------------------------------------------- /public/build/assets/DeleteBulk-6f56dab6.js: -------------------------------------------------------------------------------- 1 | import{r as g,v as h,q as w,m as k,o as f,d as C,j as B,c as D,w as s,b as n,u as r,p as I,e as i,t as e,n as S}from"./app-9cb26ff6.js";import{_ as $}from"./ConfirmationModal-8097dc2a.js";import{_ as m}from"./DangerButton-db69db22.js";import{_ as y}from"./SecondaryButton-4d541c38.js";import{T as E}from"./index-afa3307c.js";import"./Modal-d953ae84.js";const z={__name:"DeleteBulk",props:{title:String,selectedId:Object},emits:["close"],setup(p,{emit:_}){const o=p,a=g(!1),t=h({id:[]});w(()=>{a&&(t.id=o.selectedId)});const v=()=>{t.post(route("role.destroy-bulk"),{preserveScroll:!0,onSuccess:()=>{c(),_("close")},onError:()=>null,onFinish:()=>null})},c=()=>{a.value=!1};return(l,u)=>{const b=k("tooltip");return f(),C("div",null,[B((f(),D(m,{class:"rounded-none",onClick:u[0]||(u[0]=I(d=>a.value=!0,["prevent"]))},{default:s(()=>[n(r(E),{class:"w-4 h-auto"})]),_:1})),[[b,l.lang().label.delete_selected]]),n($,{show:a.value,onClose:c},{title:s(()=>[i(e(l.lang().label.delete_selected)+" "+e(o.title),1)]),content:s(()=>{var d;return[i(e(l.lang().label.delete_confirm)+" "+e((d=o.selectedId)==null?void 0:d.length)+" "+e(o.title)+"? ",1)]}),footer:s(()=>[n(y,{onClick:c},{default:s(()=>[i(e(l.lang().button.cancel),1)]),_:1}),n(m,{class:S(["ml-3",{"opacity-25":r(t).processing}]),disabled:r(t).processing,onClick:v},{default:s(()=>[i(e(l.lang().button.delete)+" "+e(r(t).processing?"...":""),1)]),_:1},8,["class","disabled"])]),_:1},8,["show"])])}}};export{z as default}; 2 | -------------------------------------------------------------------------------- /public/build/assets/DeleteBulk-896ff391.js: -------------------------------------------------------------------------------- 1 | import{r as g,v as h,q as w,m as k,o as f,d as C,j as y,c as B,w as t,b as n,u as r,p as D,e as i,t as e,n as I}from"./app-9cb26ff6.js";import{_ as S}from"./ConfirmationModal-8097dc2a.js";import{_ as m}from"./DangerButton-db69db22.js";import{_ as $}from"./SecondaryButton-4d541c38.js";import{T as E}from"./index-afa3307c.js";import"./Modal-d953ae84.js";const z={__name:"DeleteBulk",props:{title:String,selectedId:Object},emits:["close"],setup(p,{emit:_}){const o=p,a=g(!1),s=h({id:[]});w(()=>{a&&(s.id=o.selectedId)});const v=()=>{s.post(route("activity.destroy-bulk"),{preserveScroll:!0,onSuccess:()=>{c(),_("close")},onError:()=>null,onFinish:()=>null})},c=()=>{a.value=!1};return(l,u)=>{const b=k("tooltip");return f(),C("div",null,[y((f(),B(m,{class:"rounded-none",onClick:u[0]||(u[0]=D(d=>a.value=!0,["prevent"]))},{default:t(()=>[n(r(E),{class:"w-4 h-auto"})]),_:1})),[[b,l.lang().label.delete_selected]]),n(S,{show:a.value,onClose:c},{title:t(()=>[i(e(l.lang().label.delete_selected)+" "+e(o.title),1)]),content:t(()=>{var d;return[i(e(l.lang().label.delete_confirm)+" "+e((d=o.selectedId)==null?void 0:d.length)+" "+e(o.title)+"? ",1)]}),footer:t(()=>[n($,{onClick:c},{default:t(()=>[i(e(l.lang().button.cancel),1)]),_:1}),n(m,{class:I(["ml-3",{"opacity-25":r(s).processing}]),disabled:r(s).processing,onClick:v},{default:t(()=>[i(e(l.lang().button.delete)+" "+e(r(s).processing?"...":""),1)]),_:1},8,["class","disabled"])]),_:1},8,["show"])])}}};export{z as default}; 2 | -------------------------------------------------------------------------------- /public/build/assets/DeleteBulk-d065b9a7.js: -------------------------------------------------------------------------------- 1 | import{r as g,v as h,q as w,m as k,o as f,d as C,j as B,c as D,w as s,b as n,u as r,p as I,e as i,t as e,n as S}from"./app-9cb26ff6.js";import{_ as $}from"./ConfirmationModal-8097dc2a.js";import{_ as m}from"./DangerButton-db69db22.js";import{_ as y}from"./SecondaryButton-4d541c38.js";import{T as E}from"./index-afa3307c.js";import"./Modal-d953ae84.js";const z={__name:"DeleteBulk",props:{title:String,selectedId:Object},emits:["close"],setup(p,{emit:_}){const o=p,a=g(!1),t=h({id:[]});w(()=>{a&&(t.id=o.selectedId)});const v=()=>{t.post(route("user.destroy-bulk"),{preserveScroll:!0,onSuccess:()=>{c(),_("close")},onError:()=>null,onFinish:()=>null})},c=()=>{a.value=!1};return(l,u)=>{const b=k("tooltip");return f(),C("div",null,[B((f(),D(m,{class:"rounded-none",onClick:u[0]||(u[0]=I(d=>a.value=!0,["prevent"]))},{default:s(()=>[n(r(E),{class:"w-4 h-auto"})]),_:1})),[[b,l.lang().label.delete_selected]]),n($,{show:a.value,onClose:c},{title:s(()=>[i(e(l.lang().label.delete_selected)+" "+e(o.title),1)]),content:s(()=>{var d;return[i(e(l.lang().label.delete_confirm)+" "+e((d=o.selectedId)==null?void 0:d.length)+" "+e(o.title)+"? ",1)]}),footer:s(()=>[n(y,{onClick:c},{default:s(()=>[i(e(l.lang().button.cancel),1)]),_:1}),n(m,{class:S(["ml-3",{"opacity-25":r(t).processing}]),disabled:r(t).processing,onClick:v},{default:s(()=>[i(e(l.lang().button.delete)+" "+e(r(t).processing?"...":""),1)]),_:1},8,["class","disabled"])]),_:1},8,["show"])])}}};export{z as default}; 2 | -------------------------------------------------------------------------------- /resources/js/Layouts/AppLayout.vue: -------------------------------------------------------------------------------- 1 | 14 | 15 | 41 | -------------------------------------------------------------------------------- /public/build/assets/DeleteBulk-f59ba241.js: -------------------------------------------------------------------------------- 1 | import{r as g,v as h,q as w,m as k,o as m,d as C,j as B,c as D,w as s,b as n,u as r,p as I,e as i,t as e,n as S}from"./app-9cb26ff6.js";import{_ as $}from"./ConfirmationModal-8097dc2a.js";import{_ as p}from"./DangerButton-db69db22.js";import{_ as y}from"./SecondaryButton-4d541c38.js";import{T as E}from"./index-afa3307c.js";import"./Modal-d953ae84.js";const z={__name:"DeleteBulk",props:{title:String,selectedId:Object},emits:["close"],setup(f,{emit:_}){const l=f,a=g(!1),t=h({id:[]});w(()=>{a&&(t.id=l.selectedId)});const v=()=>{t.post(route("permission.destroy-bulk"),{preserveScroll:!0,onSuccess:()=>{c(),_("close")},onError:()=>null,onFinish:()=>null})},c=()=>{a.value=!1};return(o,u)=>{const b=k("tooltip");return m(),C("div",null,[B((m(),D(p,{class:"rounded-none",onClick:u[0]||(u[0]=I(d=>a.value=!0,["prevent"]))},{default:s(()=>[n(r(E),{class:"w-4 h-auto"})]),_:1})),[[b,o.lang().label.delete_selected]]),n($,{show:a.value,onClose:c},{title:s(()=>[i(e(o.lang().label.delete_selected)+" "+e(l.title),1)]),content:s(()=>{var d;return[i(e(o.lang().label.delete_confirm)+" "+e((d=l.selectedId)==null?void 0:d.length)+" "+e(l.title)+"? ",1)]}),footer:s(()=>[n(y,{onClick:c},{default:s(()=>[i(e(o.lang().button.cancel),1)]),_:1}),n(p,{class:S(["ml-3",{"opacity-25":r(t).processing}]),disabled:r(t).processing,onClick:v},{default:s(()=>[i(e(o.lang().button.delete)+" "+e(r(t).processing?"...":""),1)]),_:1},8,["class","disabled"])]),_:1},8,["show"])])}}};export{z as default}; 2 | -------------------------------------------------------------------------------- /public/build/assets/Delete-9c183e52.js: -------------------------------------------------------------------------------- 1 | import{r as b,v as g,m as h,o as d,d as w,j as k,c as C,w as o,b as l,u as n,p as $,e as t,t as s,a as B,n as D}from"./app-9cb26ff6.js";import{_ as S}from"./ConfirmationModal-8097dc2a.js";import{_ as y}from"./ActionButton-0ff23640.js";import{_ as N}from"./DangerButton-db69db22.js";import{_ as T}from"./SecondaryButton-4d541c38.js";import{T as V}from"./index-afa3307c.js";import"./Modal-d953ae84.js";const j={class:"font-black"},A={__name:"Delete",props:{title:String,permission:Object},emits:["open"],setup(f,{emit:u}){const r=f,i=b(!1),a=g({}),_=()=>{var e;a.delete(route("permission.destroy",(e=r.permission)==null?void 0:e.id),{preserveScroll:!0,onSuccess:()=>c(),onError:()=>null,onFinish:()=>null})},c=()=>{i.value=!1};return(e,p)=>{const v=h("tooltip");return d(),w("div",null,[k((d(),C(y,{variant:"danger",onClick:p[0]||(p[0]=$(m=>(i.value=!0,u("open")),["prevent"]))},{default:o(()=>[l(n(V),{class:"w-4 h-auto"})]),_:1})),[[v,e.lang().label.delete]]),l(S,{show:i.value,onClose:c},{title:o(()=>[t(s(e.lang().label.delete)+" "+s(r.title),1)]),content:o(()=>{var m;return[t(s(e.lang().label.delete_confirm)+" ",1),B("span",j,s((m=r.permission)==null?void 0:m.name),1),t("? ")]}),footer:o(()=>[l(T,{onClick:c},{default:o(()=>[t(s(e.lang().button.cancel),1)]),_:1}),l(N,{class:D(["ml-3",{"opacity-25":n(a).processing}]),disabled:n(a).processing,onClick:_},{default:o(()=>[t(s(e.lang().button.delete)+" "+s(n(a).processing?"...":""),1)]),_:1},8,["class","disabled"])]),_:1},8,["show"])])}}};export{A as default}; 2 | -------------------------------------------------------------------------------- /app/Providers/RouteServiceProvider.php: -------------------------------------------------------------------------------- 1 | configureRateLimiting(); 28 | 29 | $this->routes(function () { 30 | Route::middleware('api') 31 | ->prefix('api') 32 | ->group(base_path('routes/api.php')); 33 | 34 | Route::middleware('web') 35 | ->group(base_path('routes/web.php')); 36 | }); 37 | } 38 | 39 | /** 40 | * Configure the rate limiters for the application. 41 | */ 42 | protected function configureRateLimiting(): void 43 | { 44 | RateLimiter::for('api', function (Request $request) { 45 | return Limit::perMinute(60)->by($request->user()?->id ?: $request->ip()); 46 | }); 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /app/Providers/FortifyServiceProvider.php: -------------------------------------------------------------------------------- 1 | email; 37 | 38 | return Limit::perMinute(5)->by($email.$request->ip()); 39 | }); 40 | 41 | RateLimiter::for('two-factor', function (Request $request) { 42 | return Limit::perMinute(5)->by($request->session()->get('login.id')); 43 | }); 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /public/build/assets/Permission-e0ed2ecc.js: -------------------------------------------------------------------------------- 1 | import{_ as k}from"./DialogModal-9a2b4a3d.js";import{_ as C}from"./SecondaryButton-4d541c38.js";import{r as D,m as N,o as n,d as l,a as i,j as B,e as f,t as o,b as h,w as c,F as w,f as x,n as S}from"./app-9cb26ff6.js";import"./index-afa3307c.js";import"./Modal-d953ae84.js";const V={class:"space-y-2"},$={class:"font-bold text-primary capitalize"},j={class:"flex flex-wrap gap-4"},L={__name:"Permission",props:{title:String,caption:{type:String,default:null},permissions:Object},setup(b){var g;const a=b,m=D(!1),r=[];let u=null;(g=a.permissions)==null||g.forEach(e=>{const t=e.name.split(" ")[0],p=e.name.split(" ")[1];if(u!==t)u=t,r.push({group:u,data:[{name:p}]});else{const s=r.findIndex(d=>d.group===t);s!==-1&&r[s].data.push({name:p})}});const _=()=>{m.value=!1};return(e,t)=>{const p=N("tooltip");return n(),l("div",null,[i("div",null,[B((n(),l("p",{class:"text-primary underline cursor-pointer w-fit",onClick:t[0]||(t[0]=s=>m.value=!0)},[f(o(a.title),1)])),[[p,e.lang().label.show_permission]])]),h(k,{show:m.value,onClose:_,"max-width":"md"},{title:c(()=>[f(o(e.lang().label.permission)+" "+o(a.caption?a.caption:a.title),1)]),content:c(()=>[i("div",V,[(n(),l(w,null,x(r,(s,d)=>i("div",{class:"mt-2",key:d},[i("p",$,o(s.group),1),i("div",j,[(n(!0),l(w,null,x(s.data,(v,y)=>(n(),l("p",{key:y,class:S([v.name=="delete"?"text-red-500 font-semibold":"","mt-1 mb-4"])},o(v.name),3))),128))])])),64))])]),footer:c(()=>[h(C,{onClick:_},{default:c(()=>[f(o(e.lang().button.close),1)]),_:1})]),_:1},8,["show"])])}}};export{L as default}; 2 | -------------------------------------------------------------------------------- /database/migrations/2014_10_12_200000_add_two_factor_columns_to_users_table.php: -------------------------------------------------------------------------------- 1 | text('two_factor_secret') 17 | ->after('password') 18 | ->nullable(); 19 | 20 | $table->text('two_factor_recovery_codes') 21 | ->after('two_factor_secret') 22 | ->nullable(); 23 | 24 | if (Fortify::confirmsTwoFactorAuthentication()) { 25 | $table->timestamp('two_factor_confirmed_at') 26 | ->after('two_factor_recovery_codes') 27 | ->nullable(); 28 | } 29 | }); 30 | } 31 | 32 | /** 33 | * Reverse the migrations. 34 | */ 35 | public function down(): void 36 | { 37 | Schema::table('users', function (Blueprint $table) { 38 | $table->dropColumn(array_merge([ 39 | 'two_factor_secret', 40 | 'two_factor_recovery_codes', 41 | ], Fortify::confirmsTwoFactorAuthentication() ? [ 42 | 'two_factor_confirmed_at', 43 | ] : [])); 44 | }); 45 | } 46 | }; 47 | -------------------------------------------------------------------------------- /public/build/assets/ConfirmationModal-8097dc2a.js: -------------------------------------------------------------------------------- 1 | import{_ as i}from"./Modal-d953ae84.js";import{o as r,c,w as n,a as t,A as o}from"./app-9cb26ff6.js";const d={class:"bg-white dark:bg-slate-800 px-4 pt-5 pb-4 sm:p-6 sm:pb-4"},m={class:"sm:flex sm:items-start"},h=t("div",{class:"mx-auto shrink-0 flex items-center justify-center h-12 w-12 rounded bg-rose-100 sm:mx-0 sm:h-10 sm:w-10"},[t("svg",{class:"h-6 w-6 text-rose-600 dark:text-rose-400",xmlns:"http://www.w3.org/2000/svg",fill:"none",viewBox:"0 0 24 24","stroke-width":"1.5",stroke:"currentColor"},[t("path",{"stroke-linecap":"round","stroke-linejoin":"round",d:"M12 9v3.75m-9.303 3.376c-.866 1.5.217 3.374 1.948 3.374h14.71c1.73 0 2.813-1.874 1.948-3.374L13.949 3.378c-.866-1.5-3.032-1.5-3.898 0L2.697 16.126zM12 15.75h.007v.008H12v-.008z"})])],-1),x={class:"mt-3 text-center sm:mt-0 sm:ml-4 sm:text-left"},f={class:"text-lg font-medium text-slate-900 dark:text-slate-100"},w={class:"mt-4 text-sm text-slate-600 dark:text-slate-400"},_={class:"flex flex-row justify-end px-6 py-4 bg-slate-100 dark:bg-slate-800 text-right"},v={__name:"ConfirmationModal",props:{show:{type:Boolean,default:!1},maxWidth:{type:String,default:"2xl"},closeable:{type:Boolean,default:!1}},emits:["close"],setup(s,{emit:a}){const l=()=>{a("close")};return(e,p)=>(r(),c(i,{show:s.show,"max-width":s.maxWidth,closeable:s.closeable,onClose:l},{default:n(()=>[t("div",d,[t("div",m,[h,t("div",x,[t("h3",f,[o(e.$slots,"title")]),t("div",w,[o(e.$slots,"content")])])])]),t("div",_,[o(e.$slots,"footer")])]),_:3},8,["show","max-width","closeable"]))}};export{v as _}; 2 | -------------------------------------------------------------------------------- /resources/js/app.js: -------------------------------------------------------------------------------- 1 | import "./bootstrap"; 2 | import "../css/app.css"; 3 | import "floating-vue/dist/style.css"; 4 | 5 | import { createApp, h } from "vue"; 6 | import { createInertiaApp } from "@inertiajs/vue3"; 7 | import { resolvePageComponent } from "laravel-vite-plugin/inertia-helpers"; 8 | import { ZiggyVue } from "ziggy-js"; 9 | import { usePage } from "@inertiajs/vue3"; 10 | import FloatingVue from "floating-vue"; 11 | import Vue3Lottie from "vue3-lottie"; 12 | import "vue3-lottie/dist/style.css"; 13 | import JsonViewer from "vue-json-viewer"; 14 | import GlobalMixin from "./Mixins/global"; 15 | 16 | const pages = import.meta.glob("./Pages/**/*.vue"); 17 | 18 | createInertiaApp({ 19 | title: (title) => 20 | title 21 | ? `${title} | ${usePage().props?.app?.setting?.short_name || "Laravel"}` 22 | : `${usePage().props?.app?.setting?.short_name || "Laravel"}`, 23 | resolve: (name) => resolvePageComponent(`./Pages/${name}.vue`, pages), 24 | setup({ el, App, props, plugin }) { 25 | // ✅ Deklarasi `vueApp` terlebih dahulu sebelum mengakses `config.globalProperties` 26 | const vueApp = createApp({ render: () => h(App, props) }); 27 | 28 | // ✅ Gunakan semua plugin & mixin sebelum mounting 29 | return vueApp 30 | .use(plugin) 31 | .use(ZiggyVue) 32 | .use(FloatingVue) 33 | .use(Vue3Lottie) 34 | .use(JsonViewer) 35 | .mixin(GlobalMixin) 36 | .mount(el); 37 | }, 38 | progress: { 39 | color: "#00ba7c", 40 | }, 41 | }); 42 | -------------------------------------------------------------------------------- /resources/views/app.blade.php: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | {{ config('app.name', 'Laravel') }} 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 25 | @routes 26 | @vite(['resources/js/app.js', "resources/js/Pages/{$page['component']}.vue"]) 27 | @inertiaHead 28 | 29 | 30 | @inertia 31 | 32 | 33 | -------------------------------------------------------------------------------- /resources/js/Pages/Activity/Properties.vue: -------------------------------------------------------------------------------- 1 | 16 | -------------------------------------------------------------------------------- /resources/js/Pages/Guest/Index.vue: -------------------------------------------------------------------------------- 1 | 10 | 40 | -------------------------------------------------------------------------------- /database/seeders/PermissionSeeder.php: -------------------------------------------------------------------------------- 1 | 'user delete', 'guard_name' => 'web']); 17 | Permission::create(['name' => 'user update', 'guard_name' => 'web']); 18 | Permission::create(['name' => 'user read', 'guard_name' => 'web']); 19 | Permission::create(['name' => 'user create', 'guard_name' => 'web']); 20 | 21 | Permission::create(['name' => 'role delete', 'guard_name' => 'web']); 22 | Permission::create(['name' => 'role update', 'guard_name' => 'web']); 23 | Permission::create(['name' => 'role read', 'guard_name' => 'web']); 24 | Permission::create(['name' => 'role create', 'guard_name' => 'web']); 25 | 26 | Permission::create(['name' => 'permission delete', 'guard_name' => 'web']); 27 | Permission::create(['name' => 'permission update', 'guard_name' => 'web']); 28 | Permission::create(['name' => 'permission read', 'guard_name' => 'web']); 29 | Permission::create(['name' => 'permission create', 'guard_name' => 'web']); 30 | 31 | Permission::create(['name' => 'setting read', 'guard_name' => 'web']); 32 | 33 | Permission::create(['name' => 'activity read', 'guard_name' => 'web']); 34 | Permission::create(['name' => 'activity delete', 'guard_name' => 'web']); 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /resources/js/Components/AuthenticationCard.vue: -------------------------------------------------------------------------------- 1 | 6 | 40 | -------------------------------------------------------------------------------- /database/seeders/RoleSeeder.php: -------------------------------------------------------------------------------- 1 | 'superadmin', 18 | 'guard_name' => 'web', 19 | ]); 20 | $superadmin->givePermissionTo([ 21 | 'user delete', 22 | 'user update', 23 | 'user read', 24 | 'user create', 25 | 'role delete', 26 | 'role update', 27 | 'role read', 28 | 'role create', 29 | 'permission delete', 30 | 'permission update', 31 | 'permission read', 32 | 'permission create', 33 | 'setting read', 34 | 'activity read', 35 | 'activity delete', 36 | ]); 37 | $admin = Role::create([ 38 | 'name' => 'admin', 39 | 'guard_name' => 'web', 40 | ]); 41 | $admin->givePermissionTo([ 42 | 'user delete', 43 | 'user update', 44 | 'user read', 45 | 'user create', 46 | 'role read', 47 | 'permission read', 48 | ]); 49 | $operator = Role::create([ 50 | 'name' => 'operator', 51 | 'guard_name' => 'web', 52 | ]); 53 | 54 | $operator->givePermissionTo([ 55 | 'user read', 56 | 'role read', 57 | 'permission read', 58 | ]); 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /config/activitylog.php: -------------------------------------------------------------------------------- 1 | env('ACTIVITY_LOGGER_ENABLED', true), 9 | 10 | /* 11 | * When the clean-command is executed, all recording activities older than 12 | * the number of days specified here will be deleted. 13 | */ 14 | 'delete_records_older_than_days' => 365, 15 | 16 | /* 17 | * If no log name is passed to the activity() helper 18 | * we use this default log name. 19 | */ 20 | 'default_log_name' => 'system', 21 | 22 | /* 23 | * You can specify an auth driver here that gets user models. 24 | * If this is null we'll use the current Laravel auth driver. 25 | */ 26 | 'default_auth_driver' => null, 27 | 28 | /* 29 | * If set to true, the subject returns soft deleted models. 30 | */ 31 | 'subject_returns_soft_deleted_models' => false, 32 | 33 | /* 34 | * This model will be used to log activity. 35 | * It should implement the Spatie\Activitylog\Contracts\Activity interface 36 | * and extend Illuminate\Database\Eloquent\Model. 37 | */ 38 | 'activity_model' => \Spatie\Activitylog\Models\Activity::class, 39 | 40 | /* 41 | * This is the name of the table that will be created by the migration and 42 | * used by the Activity model shipped with this package. 43 | */ 44 | 'table_name' => 'activity_log', 45 | 46 | /* 47 | * This is the database connection that will be used by the migration and 48 | * the Activity model shipped with this package. In case it's not set 49 | * Laravel's database.default will be used instead. 50 | */ 51 | 'database_connection' => env('ACTIVITY_LOGGER_DB_CONNECTION'), 52 | ]; 53 | -------------------------------------------------------------------------------- /config/hashing.php: -------------------------------------------------------------------------------- 1 | 'bcrypt', 19 | 20 | /* 21 | |-------------------------------------------------------------------------- 22 | | Bcrypt Options 23 | |-------------------------------------------------------------------------- 24 | | 25 | | Here you may specify the configuration options that should be used when 26 | | passwords are hashed using the Bcrypt algorithm. This will allow you 27 | | to control the amount of time it takes to hash the given password. 28 | | 29 | */ 30 | 31 | 'bcrypt' => [ 32 | 'rounds' => env('BCRYPT_ROUNDS', 10), 33 | ], 34 | 35 | /* 36 | |-------------------------------------------------------------------------- 37 | | Argon Options 38 | |-------------------------------------------------------------------------- 39 | | 40 | | Here you may specify the configuration options that should be used when 41 | | passwords are hashed using the Argon algorithm. These will allow you 42 | | to control the amount of time it takes to hash the given password. 43 | | 44 | */ 45 | 46 | 'argon' => [ 47 | 'memory' => 65536, 48 | 'threads' => 1, 49 | 'time' => 4, 50 | ], 51 | 52 | ]; 53 | -------------------------------------------------------------------------------- /bootstrap/app.php: -------------------------------------------------------------------------------- 1 | singleton( 30 | Illuminate\Contracts\Http\Kernel::class, 31 | App\Http\Kernel::class 32 | ); 33 | 34 | $app->singleton( 35 | Illuminate\Contracts\Console\Kernel::class, 36 | App\Console\Kernel::class 37 | ); 38 | 39 | $app->singleton( 40 | Illuminate\Contracts\Debug\ExceptionHandler::class, 41 | App\Exceptions\Handler::class 42 | ); 43 | 44 | /* 45 | |-------------------------------------------------------------------------- 46 | | Return The Application 47 | |-------------------------------------------------------------------------- 48 | | 49 | | This script returns the application instance. The instance is given to 50 | | the calling script so we can separate the building of the instances 51 | | from the actual running of the application and sending responses. 52 | | 53 | */ 54 | 55 | return $app; 56 | -------------------------------------------------------------------------------- /tests/Feature/UpdatePasswordTest.php: -------------------------------------------------------------------------------- 1 | actingAs($user = User::factory()->create()); 17 | 18 | $response = $this->put('/user/password', [ 19 | 'current_password' => 'password', 20 | 'password' => 'new-password', 21 | 'password_confirmation' => 'new-password', 22 | ]); 23 | 24 | $this->assertTrue(Hash::check('new-password', $user->fresh()->password)); 25 | } 26 | 27 | public function test_current_password_must_be_correct(): void 28 | { 29 | $this->actingAs($user = User::factory()->create()); 30 | 31 | $response = $this->put('/user/password', [ 32 | 'current_password' => 'wrong-password', 33 | 'password' => 'new-password', 34 | 'password_confirmation' => 'new-password', 35 | ]); 36 | 37 | $response->assertSessionHasErrors(); 38 | 39 | $this->assertTrue(Hash::check('password', $user->fresh()->password)); 40 | } 41 | 42 | public function test_new_passwords_must_match(): void 43 | { 44 | $this->actingAs($user = User::factory()->create()); 45 | 46 | $response = $this->put('/user/password', [ 47 | 'current_password' => 'password', 48 | 'password' => 'new-password', 49 | 'password_confirmation' => 'wrong-password', 50 | ]); 51 | 52 | $response->assertSessionHasErrors(); 53 | 54 | $this->assertTrue(Hash::check('password', $user->fresh()->password)); 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /tests/Feature/RegistrationTest.php: -------------------------------------------------------------------------------- 1 | markTestSkipped('Registration support is not enabled.'); 19 | 20 | return; 21 | } 22 | 23 | $response = $this->get('/register'); 24 | 25 | $response->assertStatus(200); 26 | } 27 | 28 | public function test_registration_screen_cannot_be_rendered_if_support_is_disabled(): void 29 | { 30 | if (Features::enabled(Features::registration())) { 31 | $this->markTestSkipped('Registration support is enabled.'); 32 | 33 | return; 34 | } 35 | 36 | $response = $this->get('/register'); 37 | 38 | $response->assertStatus(404); 39 | } 40 | 41 | public function test_new_users_can_register(): void 42 | { 43 | if (! Features::enabled(Features::registration())) { 44 | $this->markTestSkipped('Registration support is not enabled.'); 45 | 46 | return; 47 | } 48 | 49 | $response = $this->post('/register', [ 50 | 'name' => 'Test User', 51 | 'email' => 'test@example.com', 52 | 'password' => 'password', 53 | 'password_confirmation' => 'password', 54 | 'terms' => Jetstream::hasTermsAndPrivacyPolicyFeature(), 55 | ]); 56 | 57 | $this->assertAuthenticated(); 58 | $response->assertRedirect(RouteServiceProvider::HOME); 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /public/build/assets/Create-1f78ecde.js: -------------------------------------------------------------------------------- 1 | import{r as b,v,o as h,d as w,b as s,w as l,u as a,a as m,t as n,p as d,e as c,n as k}from"./app-9cb26ff6.js";import{_ as C}from"./DialogModal-9a2b4a3d.js";import{_ as $}from"./InputError-71f77be1.js";import{_ as y}from"./InputLabel-ef1a5601.js";import{_ as f}from"./PrimaryButton-0fb1f193.js";import{_ as S}from"./SecondaryButton-4d541c38.js";import{_ as V}from"./TextInput-f97bd710.js";import{P as B}from"./index-afa3307c.js";import"./Modal-d953ae84.js";const N={class:"hidden md:block"},E=["onSubmit"],M={class:"space-y-1"},x={__name:"Create",props:{title:String},setup(_){const g=_,t=b(!1),e=v({name:"",guard_name:"web"}),u=()=>{e.post(route("permission.store"),{preserveScroll:!0,onSuccess:()=>i(),onError:()=>null,onFinish:()=>null})},i=()=>{t.value=!1,e.errors={},e.reset()};return(o,r)=>(h(),w("div",null,[s(f,{class:"flex rounded-none items-center justify-start gap-2",onClick:r[0]||(r[0]=d(p=>t.value=!0,["prevent"]))},{default:l(()=>[s(a(B),{class:"w-4 h-auto"}),m("span",N,n(o.lang().button.add),1)]),_:1}),s(C,{show:t.value,onClose:i},{title:l(()=>[c(n(o.lang().label.add)+" "+n(g.title),1)]),content:l(()=>[m("form",{class:"space-y-2",onSubmit:d(u,["prevent"])},[m("div",M,[s(y,{for:"name",value:o.lang().label.name},null,8,["value"]),s(V,{id:"name",modelValue:a(e).name,"onUpdate:modelValue":r[1]||(r[1]=p=>a(e).name=p),type:"text",class:"block w-full",autocomplete:"name",placeholder:o.lang().placeholder.permission_name,error:a(e).errors.name},null,8,["modelValue","placeholder","error"]),s($,{message:a(e).errors.name},null,8,["message"])])],40,E)]),footer:l(()=>[s(S,{onClick:i},{default:l(()=>[c(n(o.lang().button.cancel),1)]),_:1}),s(f,{class:k(["ml-3",{"opacity-25":a(e).processing}]),disabled:a(e).processing,onClick:u},{default:l(()=>[c(n(o.lang().button.save)+" "+n(a(e).processing?"...":""),1)]),_:1},8,["class","disabled"])]),_:1},8,["show"])]))}};export{x as default}; 2 | -------------------------------------------------------------------------------- /artisan: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env php 2 | make(Illuminate\Contracts\Console\Kernel::class); 34 | 35 | $status = $kernel->handle( 36 | $input = new Symfony\Component\Console\Input\ArgvInput, 37 | new Symfony\Component\Console\Output\ConsoleOutput 38 | ); 39 | 40 | /* 41 | |-------------------------------------------------------------------------- 42 | | Shutdown The Application 43 | |-------------------------------------------------------------------------- 44 | | 45 | | Once Artisan has finished running, we will fire off the shutdown events 46 | | so that any final work may be done by the application before we shut 47 | | down the process. This is the last thing to happen to the request. 48 | | 49 | */ 50 | 51 | $kernel->terminate($input, $status); 52 | 53 | exit($status); 54 | -------------------------------------------------------------------------------- /public/build/assets/ConfirmPassword-534de2b7.js: -------------------------------------------------------------------------------- 1 | import{v as d,r as c,o as f,d as u,b as a,u as o,X as _,w as t,a as e,t as l,n as w,e as b,p as g,F as h}from"./app-9cb26ff6.js";import{_ as v}from"./AuthenticationCard-4227316f.js";import{_ as x}from"./AuthenticationCardLogo-a2386df2.js";import{_ as y}from"./InputError-71f77be1.js";import{_ as V}from"./InputLabel-ef1a5601.js";import{_ as $}from"./PrimaryButton-0fb1f193.js";import{_ as k}from"./TextInput-f97bd710.js";import"./SwitchLocale-b9e142e8.js";import"./SwitchDarkMode-e6f628a6.js";import"./index-afa3307c.js";import"./ApplicationLogo-914092fa.js";import"./_plugin-vue_export-helper-c27b6911.js";const B={class:"flex flex-col mb-4"},C={class:"text-primary font-semibold text-xl"},F={class:"text-slate-400"},N=["onSubmit"],S={class:"flex justify-end mt-4"},H={__name:"ConfirmPassword",setup(I){const s=d({password:""}),n=c(null),m=()=>{s.post(route("password.confirm"),{onFinish:()=>{s.reset(),n.value.focus()}})};return(r,i)=>(f(),u(h,null,[a(o(_),{title:"lang().label.secure_area"}),a(v,null,{logo:t(()=>[a(x)]),default:t(()=>[e("div",B,[e("h2",C,l(r.lang().label.confirm_password),1),e("small",F,l(r.lang().label.confirm_password_caption),1)]),e("form",{onSubmit:g(m,["prevent"])},[e("div",null,[a(V,{for:"password",value:r.lang().label.password},null,8,["value"]),a(k,{id:"password",ref_key:"passwordInput",ref:n,modelValue:o(s).password,"onUpdate:modelValue":i[0]||(i[0]=p=>o(s).password=p),type:"password",class:"mt-1 block w-full",required:"",placeholder:r.lang().placeholder.password,error:o(s).errors.password,autocomplete:"current-password",autofocus:""},null,8,["modelValue","placeholder","error"]),a(y,{class:"mt-2",message:o(s).errors.password},null,8,["message"])]),e("div",S,[a($,{class:w(["ml-4",{"opacity-25":o(s).processing}]),disabled:o(s).processing},{default:t(()=>[b(l(r.lang().button.confirm)+" "+l(o(s).processing?"...":""),1)]),_:1},8,["class","disabled"])])],40,N)]),_:1})],64))}};export{H as default}; 2 | -------------------------------------------------------------------------------- /public/index.php: -------------------------------------------------------------------------------- 1 | make(Kernel::class); 50 | 51 | $response = $kernel->handle( 52 | $request = Request::capture() 53 | )->send(); 54 | 55 | $kernel->terminate($request, $response); 56 | -------------------------------------------------------------------------------- /app/Http/Controllers/Api/UserController.php: -------------------------------------------------------------------------------- 1 | json([ 16 | 'data' => User::with('roles')->find(auth()->user()->id), 17 | 'message' => "Data user loged in", 18 | 'status' => Response::HTTP_OK, 19 | 'access_token' => "", 20 | 'token_type' => 'Bearer' 21 | ]); 22 | } 23 | 24 | public function updatePassword(Request $request){ 25 | try { 26 | $user = User::find(auth()->user()->id); 27 | if (!Hash::check($request->password, $user->password)) 28 | { 29 | return response() 30 | ->json(['message' => 'Unauthorized'], Response::HTTP_UNAUTHORIZED); 31 | } 32 | $user->update([ 33 | 'password' => Hash::make($request->new_password) 34 | ]); 35 | return response()->json([ 36 | 'data' => User::with('roles')->find(auth()->user()->id), 37 | 'message' => "Data user loged in", 38 | 'status' => Response::HTTP_OK, 39 | 'access_token' => "", 40 | 'token_type' => 'Bearer' 41 | ]); 42 | } catch (\Throwable $th) { 43 | return response()->json([ 44 | 'data' => [], 45 | 'message' => $th->getMessage(), 46 | 'status' => $th->getCode(), 47 | 'access_token' => "", 48 | 'token_type' => 'Bearer' 49 | ], Response::HTTP_INTERNAL_SERVER_ERROR); 50 | } 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /app/Actions/Fortify/UpdateUserProfileInformation.php: -------------------------------------------------------------------------------- 1 | $input 17 | */ 18 | public function update(User $user, array $input): void 19 | { 20 | Validator::make($input, [ 21 | 'name' => ['required', 'string', 'max:255'], 22 | 'email' => ['required', 'email', 'max:255', Rule::unique('users')->ignore($user->id)], 23 | 'photo' => ['nullable', 'mimes:jpg,jpeg,png', 'max:1024'], 24 | ])->validateWithBag('updateProfileInformation'); 25 | 26 | if (isset($input['photo'])) { 27 | $user->updateProfilePhoto($input['photo']); 28 | } 29 | 30 | if ($input['email'] !== $user->email && 31 | $user instanceof MustVerifyEmail) { 32 | $this->updateVerifiedUser($user, $input); 33 | } else { 34 | $user->forceFill([ 35 | 'name' => $input['name'], 36 | 'email' => $input['email'], 37 | ])->save(); 38 | } 39 | } 40 | 41 | /** 42 | * Update the given verified user's profile information. 43 | * 44 | * @param array $input 45 | */ 46 | protected function updateVerifiedUser(User $user, array $input): void 47 | { 48 | $user->forceFill([ 49 | 'name' => $input['name'], 50 | 'email' => $input['email'], 51 | 'email_verified_at' => null, 52 | ])->save(); 53 | 54 | $user->sendEmailVerificationNotification(); 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /resources/js/Components/DialogModal.vue: -------------------------------------------------------------------------------- 1 | 26 | 27 | 59 | -------------------------------------------------------------------------------- /public/build/assets/ForgotPassword-3b3afe78.js: -------------------------------------------------------------------------------- 1 | import{v as p,o as n,d,b as a,u as s,X as _,w as r,a as o,t as l,g as f,n as g,e as b,p as h,F as w}from"./app-9cb26ff6.js";import{_ as v}from"./AuthenticationCard-4227316f.js";import{_ as x}from"./AuthenticationCardLogo-a2386df2.js";import{_ as V}from"./InputError-71f77be1.js";import{_ as k}from"./InputLabel-ef1a5601.js";import{_ as y}from"./PrimaryButton-0fb1f193.js";import{_ as $}from"./TextInput-f97bd710.js";import"./SwitchLocale-b9e142e8.js";import"./SwitchDarkMode-e6f628a6.js";import"./index-afa3307c.js";import"./ApplicationLogo-914092fa.js";import"./_plugin-vue_export-helper-c27b6911.js";const N={class:"flex flex-col mb-4"},S={class:"text-primary font-semibold text-xl"},B={class:"text-slate-400"},C={key:0,class:"mb-4 font-medium text-sm text-green-600 dark:text-green-400"},F=["onSubmit"],j={class:"flex items-center justify-end mt-4"},I={__name:"ForgotPassword",props:{status:String},setup(i){const e=p({email:""}),c=()=>{e.post(route("password.email"))};return(t,m)=>(n(),d(w,null,[a(s(_),{title:t.lang().label.forgot_password},null,8,["title"]),a(v,null,{logo:r(()=>[a(x)]),default:r(()=>[o("div",N,[o("h2",S,l(t.lang().label.forgot_password),1),o("small",B,l(t.lang().label.forgot_password_caption),1)]),i.status?(n(),d("div",C,l(i.status),1)):f("",!0),o("form",{onSubmit:h(c,["prevent"])},[o("div",null,[a(k,{for:"email",value:t.lang().label.email},null,8,["value"]),a($,{id:"email",modelValue:s(e).email,"onUpdate:modelValue":m[0]||(m[0]=u=>s(e).email=u),type:"email",class:"mt-1 block w-full",required:"",autofocus:"",autocomplete:"username",placeholder:t.lang().placeholder.email,error:s(e).errors.email},null,8,["modelValue","placeholder","error"]),a(V,{class:"mt-2",message:s(e).errors.email},null,8,["message"])]),o("div",j,[a(y,{class:g({"opacity-25":s(e).processing}),disabled:s(e).processing},{default:r(()=>[b(l(t.lang().button.email_password_reset_link)+" "+l(s(e).processing?"...":""),1)]),_:1},8,["class","disabled"])])],40,F)]),_:1})],64))}};export{I as default}; 2 | -------------------------------------------------------------------------------- /public/build/assets/Show-c2090c6a.js: -------------------------------------------------------------------------------- 1 | import{_ as p}from"./AppLayout-706b8ec5.js";import c from"./DeleteUserForm-a3ec2a2d.js";import l from"./LogoutOtherBrowserSessionsForm-923776ce.js";import{S as r}from"./SectionBorder-6e176b61.js";import u from"./TwoFactorAuthenticationForm-c05a2355.js";import f from"./UpdatePasswordForm-e7555950.js";import d from"./UpdateProfileInformationForm-7c63f334.js";import{o as e,c as _,w as n,a as i,t as g,d as s,b as t,g as a,F as h}from"./app-9cb26ff6.js";import"./Toast-48e493b9.js";import"./index-afa3307c.js";import"./_plugin-vue_export-helper-c27b6911.js";import"./SwitchDarkMode-e6f628a6.js";import"./SwitchLocale-b9e142e8.js";import"./ApplicationLogo-914092fa.js";import"./ActionSection-bb60ff24.js";import"./SectionTitle-1f89132a.js";import"./DangerButton-db69db22.js";import"./DialogModal-9a2b4a3d.js";import"./Modal-d953ae84.js";import"./InputError-71f77be1.js";import"./SecondaryButton-4d541c38.js";import"./TextInput-f97bd710.js";import"./ActionMessage-dafe95ee.js";import"./PrimaryButton-0fb1f193.js";import"./InputLabel-ef1a5601.js";import"./FormSection-fd684e6c.js";const $={class:"max-w-7xl mx-auto py-10 sm:px-6 lg:px-8"},w={key:0},k={key:1},y={key:2},R={__name:"Show",props:{confirmsTwoFactorAuthentication:Boolean,sessions:Array},setup(m){return(o,B)=>(e(),_(p,{title:o.lang().label.profile},{title:n(()=>[i("span",null,g(o.lang().label.profile),1)]),default:n(()=>[i("div",null,[i("div",$,[o.$page.props.jetstream.canUpdateProfileInformation?(e(),s("div",w,[t(d,{user:o.$page.props.auth.user},null,8,["user"]),t(r)])):a("",!0),o.$page.props.jetstream.canUpdatePassword?(e(),s("div",k,[t(f,{class:"mt-10 sm:mt-0"}),t(r)])):a("",!0),o.$page.props.jetstream.canManageTwoFactorAuthentication?(e(),s("div",y,[t(u,{"requires-confirmation":m.confirmsTwoFactorAuthentication,class:"mt-10 sm:mt-0"},null,8,["requires-confirmation"]),t(r)])):a("",!0),t(l,{sessions:m.sessions,class:"mt-10 sm:mt-0"},null,8,["sessions"]),o.$page.props.jetstream.hasAccountDeletionFeatures?(e(),s(h,{key:3},[t(r),t(c,{class:"mt-10 sm:mt-0"})],64)):a("",!0)])])]),_:1},8,["title"]))}};export{R as default}; 2 | -------------------------------------------------------------------------------- /public/build/assets/Modal-d953ae84.js: -------------------------------------------------------------------------------- 1 | import{i as x,E as w,G as y,z as f,o as v,c as h,b as o,w as l,j as n,a,T as c,k as i,n as p,u as b,A as k,g,Q as _}from"./app-9cb26ff6.js";const B={class:"fixed inset-0 overflow-y-auto px-4 py-6 sm:px-0 z-50","scroll-region":""},C=a("div",{class:"absolute inset-0 bg-slate-500 dark:bg-slate-900 opacity-75"},null,-1),E=[C],S={__name:"Modal",props:{show:{type:Boolean,default:!1},maxWidth:{type:String,default:"2xl"},closeable:{type:Boolean,default:!0}},emits:["close"],setup(e,{emit:d}){const s=e;x(()=>s.show,()=>{s.show?document.body.style.overflow="hidden":document.body.style.overflow=null});const r=()=>{s.closeable&&d("close")},m=t=>{t.key==="Escape"&&s.show&&r()};w(()=>document.addEventListener("keydown",m)),y(()=>{document.removeEventListener("keydown",m),document.body.style.overflow=null});const u=f(()=>({sm:"sm:max-w-sm",md:"sm:max-w-md",lg:"sm:max-w-lg",xl:"sm:max-w-xl","2xl":"sm:max-w-2xl","3xl":"sm:max-w-3xl","4xl":"sm:max-w-4xl","5xl":"sm:max-w-5xl","6xl":"sm:max-w-6xl","7xl":"sm:max-w-7xl"})[s.maxWidth]);return(t,z)=>(v(),h(_,{to:"body"},[o(c,{"leave-active-class":"duration-200"},{default:l(()=>[n(a("div",B,[o(c,{"enter-active-class":"ease-out duration-300","enter-from-class":"opacity-0","enter-to-class":"opacity-100","leave-active-class":"ease-in duration-200","leave-from-class":"opacity-100","leave-to-class":"opacity-0"},{default:l(()=>[n(a("div",{class:"fixed inset-0 transform transition-all",onClick:r},E,512),[[i,e.show]])]),_:1}),o(c,{"enter-active-class":"ease-out duration-300","enter-from-class":"opacity-0 translate-y-4 sm:translate-y-0 sm:scale-95","enter-to-class":"opacity-100 translate-y-0 sm:scale-100","leave-active-class":"ease-in duration-200","leave-from-class":"opacity-100 translate-y-0 sm:scale-100","leave-to-class":"opacity-0 translate-y-4 sm:translate-y-0 sm:scale-95"},{default:l(()=>[n(a("div",{class:p(["mb-6 bg-white dark:bg-slate-800 rounded overflow-hidden shadow transform transition-all sm:w-full sm:mx-auto",b(u)])},[e.show?k(t.$slots,"default",{key:0}):g("",!0)],2),[[i,e.show]])]),_:3})],512),[[i,e.show]])]),_:3})]))}};export{S as _}; 2 | -------------------------------------------------------------------------------- /public/build/assets/DeleteUserForm-a3ec2a2d.js: -------------------------------------------------------------------------------- 1 | import{r as p,v as g,o as b,c as v,w as s,e as a,t as o,a as u,b as l,u as r,C as h,n as k}from"./app-9cb26ff6.js";import{_ as y}from"./ActionSection-bb60ff24.js";import{_}from"./DangerButton-db69db22.js";import{_ as C}from"./DialogModal-9a2b4a3d.js";import{_ as V}from"./InputError-71f77be1.js";import{_ as $}from"./SecondaryButton-4d541c38.js";import{_ as U}from"./TextInput-f97bd710.js";import"./SectionTitle-1f89132a.js";import"./_plugin-vue_export-helper-c27b6911.js";import"./index-afa3307c.js";import"./Modal-d953ae84.js";const D={class:"max-w-xl text-sm text-slate-600 dark:text-slate-400"},B={class:"mt-5"},K={class:"mt-4"},A={__name:"DeleteUserForm",setup(N){const n=p(!1),c=p(null),e=g({password:""}),f=()=>{n.value=!0,setTimeout(()=>c.value.focus(),250)},d=()=>{e.delete(route("current-user.destroy"),{preserveScroll:!0,onSuccess:()=>i(),onError:()=>c.value.focus(),onFinish:()=>e.reset()})},i=()=>{n.value=!1,e.reset()};return(t,m)=>(b(),v(y,null,{title:s(()=>[a(o(t.lang().label.delete_account),1)]),description:s(()=>[a(o(t.lang().label.delete_account_description),1)]),content:s(()=>[u("div",D,o(t.lang().label.delete_account_content),1),u("div",B,[l(_,{onClick:f},{default:s(()=>[a(o(t.lang().button.delete_account),1)]),_:1})]),l(C,{show:n.value,onClose:i},{title:s(()=>[a(o(t.lang().label.delete_account),1)]),content:s(()=>[a(o(t.lang().label.delete_account_confirm)+" ",1),u("div",K,[l(U,{ref_key:"passwordInput",ref:c,modelValue:r(e).password,"onUpdate:modelValue":m[0]||(m[0]=w=>r(e).password=w),type:"password",class:"mt-1 block w-full",placeholder:"Password",autocomplete:"current-password",onKeyup:h(d,["enter"]),error:r(e).errors.password},null,8,["modelValue","onKeyup","error"]),l(V,{message:r(e).errors.password,class:"mt-2"},null,8,["message"])])]),footer:s(()=>[l($,{onClick:i},{default:s(()=>[a(o(t.lang().button.cancel),1)]),_:1}),l(_,{class:k(["ml-3",{"opacity-25":r(e).processing}]),disabled:r(e).processing,onClick:d},{default:s(()=>[a(o(t.lang().button.delete_account)+" "+o(r(e).processing?"...":""),1)]),_:1},8,["class","disabled"])]),_:1},8,["show"])]),_:1}))}};export{A as default}; 2 | -------------------------------------------------------------------------------- /public/build/assets/Edit-d7112c95.js: -------------------------------------------------------------------------------- 1 | import{r as w,v as $,B as k,m as C,o as f,d as S,j as V,c as y,w as l,b as o,u as a,p as _,e as c,t as n,a as v,n as B}from"./app-9cb26ff6.js";import{_ as j}from"./DialogModal-9a2b4a3d.js";import{_ as D}from"./InputError-71f77be1.js";import{_ as E}from"./InputLabel-ef1a5601.js";import{_ as N}from"./ActionButton-0ff23640.js";import{_ as M}from"./PrimaryButton-0fb1f193.js";import{_ as O}from"./SecondaryButton-4d541c38.js";import{_ as U}from"./TextInput-f97bd710.js";import{a as z}from"./index-afa3307c.js";import"./Modal-d953ae84.js";const F=["onSubmit"],I={class:"space-y-1"},R={__name:"Edit",props:{title:String,roles:Object,permission:Object},emits:["open"],setup(b,{emit:g}){var u;const r=b,t=w(!1),s=$({name:"",guard_name:"web"});k(()=>{var e;t&&(s.name=(e=r.permission)==null?void 0:e.name)});const p=()=>{var e;s.put(route("permission.update",(e=r.permission)==null?void 0:e.id),{preserveScroll:!0,onSuccess:()=>m(),onError:()=>null,onFinish:()=>null})},m=()=>{t.value=!1,s.errors={},s.reset()};return(u=r.roles)==null||u.map(e=>({label:e.name,value:e.name})),(e,i)=>{const h=C("tooltip");return f(),S("div",null,[V((f(),y(N,{onClick:i[0]||(i[0]=_(d=>(t.value=!0,g("open")),["prevent"]))},{default:l(()=>[o(a(z),{class:"w-4 h-auto"})]),_:1})),[[h,e.lang().label.edit]]),o(j,{show:t.value,onClose:m},{title:l(()=>[c(n(e.lang().label.edit)+" "+n(r.title),1)]),content:l(()=>[v("form",{class:"space-y-2",onSubmit:_(p,["prevent"])},[v("div",I,[o(E,{for:"name",value:e.lang().label.name},null,8,["value"]),o(U,{id:"name",modelValue:a(s).name,"onUpdate:modelValue":i[1]||(i[1]=d=>a(s).name=d),type:"text",class:"block w-full",autocomplete:"name",placeholder:e.lang().placeholder.permission_name,error:a(s).errors.name},null,8,["modelValue","placeholder","error"]),o(D,{message:a(s).errors.name},null,8,["message"])])],40,F)]),footer:l(()=>[o(O,{onClick:m},{default:l(()=>[c(n(e.lang().button.cancel),1)]),_:1}),o(M,{class:B(["ml-3",{"opacity-25":a(s).processing}]),disabled:a(s).processing,onClick:p},{default:l(()=>[c(n(e.lang().button.save)+" "+n(a(s).processing?"...":""),1)]),_:1},8,["class","disabled"])]),_:1},8,["show"])])}}};export{R as default}; 2 | -------------------------------------------------------------------------------- /database/factories/UserFactory.php: -------------------------------------------------------------------------------- 1 | 24 | */ 25 | public function definition(): array 26 | { 27 | return [ 28 | 'name' => $this->faker->name(), 29 | 'email' => $this->faker->unique()->safeEmail(), 30 | 'email_verified_at' => now(), 31 | 'password' => '$2y$10$92IXUNpkjO0rOQ5byMi.Ye4oKoEa3Ro9llC/.og/at2.uheWG/igi', // password 32 | 'two_factor_secret' => null, 33 | 'two_factor_recovery_codes' => null, 34 | 'remember_token' => Str::random(10), 35 | 'profile_photo_path' => null, 36 | 'current_team_id' => null, 37 | ]; 38 | } 39 | 40 | /** 41 | * Indicate that the model's email address should be unverified. 42 | */ 43 | public function unverified(): static 44 | { 45 | return $this->state(function (array $attributes) { 46 | return [ 47 | 'email_verified_at' => null, 48 | ]; 49 | }); 50 | } 51 | 52 | /** 53 | * Indicate that the user should have a personal team. 54 | */ 55 | public function withPersonalTeam(): static 56 | { 57 | if (! Features::hasTeamFeatures()) { 58 | return $this->state([]); 59 | } 60 | 61 | return $this->has( 62 | Team::factory() 63 | ->state(function (array $attributes, User $user) { 64 | return ['name' => $user->name.'\'s Team', 'user_id' => $user->id, 'personal_team' => true]; 65 | }), 66 | 'ownedTeams' 67 | ); 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /resources/js/Components/ConfirmationModal.vue: -------------------------------------------------------------------------------- 1 | 25 | 26 | 56 | -------------------------------------------------------------------------------- /public/build/assets/VerifyEmail-fb5fdae9.js: -------------------------------------------------------------------------------- 1 | import{v as _,z as g,o as n,d as f,b as o,u as t,X as h,w as i,a,t as s,g as b,n as v,e as r,x as m,p as x,F as k}from"./app-9cb26ff6.js";import{_ as y}from"./AuthenticationCard-4227316f.js";import{_ as w}from"./AuthenticationCardLogo-a2386df2.js";import{_ as S}from"./PrimaryButton-0fb1f193.js";import"./SwitchLocale-b9e142e8.js";import"./SwitchDarkMode-e6f628a6.js";import"./index-afa3307c.js";import"./ApplicationLogo-914092fa.js";import"./_plugin-vue_export-helper-c27b6911.js";const V={class:"flex flex-col mb-4"},N={class:"text-primary font-semibold text-xl"},B={class:"text-slate-400"},C={key:0,class:"mb-4 font-medium text-sm text-green-600 dark:text-green-400"},$=["onSubmit"],z={class:"mt-4 flex flex-col gap-4 items-center justify-between"},G={__name:"VerifyEmail",props:{status:String},setup(c){const u=c,l=_({}),d=()=>{l.post(route("verification.send"))},p=g(()=>u.status==="verification-link-sent");return(e,E)=>(n(),f(k,null,[o(t(h),{title:e.lang().label.email_verification},null,8,["title"]),o(y,null,{logo:i(()=>[o(w)]),default:i(()=>[a("div",V,[a("h2",N,s(e.lang().label.verify_email),1),a("small",B,s(e.lang().label.verify_email_caption),1)]),t(p)?(n(),f("div",C,s(e.lang().label.email_verification_link),1)):b("",!0),a("form",{onSubmit:x(d,["prevent"])},[a("div",z,[o(S,{class:v({"opacity-25":t(l).processing}),disabled:t(l).processing},{default:i(()=>[r(s(e.lang().button.resend_email_verification_link)+" "+s(t(l).processing?"...":""),1)]),_:1},8,["class","disabled"]),a("div",null,[o(t(m),{href:e.route("profile.show"),class:"underline text-sm text-slate-600 dark:text-slate-400 hover:text-slate-900 dark:hover:text-slate-100 rounded focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-primary dark:focus:ring-offset-slate-800"},{default:i(()=>[r(s(e.lang().label.edit_profile),1)]),_:1},8,["href"]),o(t(m),{href:e.route("logout"),method:"post",as:"button",class:"underline text-sm text-slate-600 dark:text-slate-400 hover:text-slate-900 dark:hover:text-slate-100 rounded focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-primary dark:focus:ring-offset-slate-800 ml-2"},{default:i(()=>[r(s(e.lang().label.logout),1)]),_:1},8,["href"])])])],40,$)]),_:1})],64))}};export{G as default}; 2 | -------------------------------------------------------------------------------- /resources/js/Pages/Auth/ForgotPassword.vue: -------------------------------------------------------------------------------- 1 | 22 | 23 | 54 | -------------------------------------------------------------------------------- /resources/js/Pages/Profile/Show.vue: -------------------------------------------------------------------------------- 1 | 15 | 16 | 54 | -------------------------------------------------------------------------------- /resources/js/Pages/Auth/ConfirmPassword.vue: -------------------------------------------------------------------------------- 1 | 27 | 28 | 56 | -------------------------------------------------------------------------------- /resources/js/Components/DeleteComponent.vue: -------------------------------------------------------------------------------- 1 | 32 | 68 | -------------------------------------------------------------------------------- /resources/js/Pages/User/Delete.vue: -------------------------------------------------------------------------------- 1 | 32 | 68 | --------------------------------------------------------------------------------