├── public ├── favicon.ico ├── robots.txt ├── js │ └── filament │ │ ├── forms │ │ └── components │ │ │ ├── textarea.js │ │ │ ├── tags-input.js │ │ │ └── key-value.js │ │ ├── tables │ │ └── components │ │ │ └── table.js │ │ └── support │ │ └── async-alpine.js ├── .htaccess ├── index.php ├── css │ └── filament │ │ └── support │ │ └── support.css └── assets │ ├── head.svg │ └── head-filled.svg ├── resources ├── css │ └── app.css ├── js │ ├── app.js │ └── bootstrap.js └── views │ ├── components │ ├── mail │ │ ├── text │ │ │ ├── panel.blade.php │ │ │ ├── table.blade.php │ │ │ ├── footer.blade.php │ │ │ ├── subcopy.blade.php │ │ │ ├── button.blade.php │ │ │ ├── header.blade.php │ │ │ ├── layout.blade.php │ │ │ └── message.blade.php │ │ └── html │ │ │ ├── message.blade.php │ │ │ ├── table.blade.php │ │ │ ├── subcopy.blade.php │ │ │ ├── footer.blade.php │ │ │ ├── header.blade.php │ │ │ ├── panel.blade.php │ │ │ ├── button.blade.php │ │ │ ├── layout.blade.php │ │ │ └── themes │ │ │ └── default.css │ ├── footer.blade.php │ ├── task-list.blade.php │ ├── navbar.blade.php │ └── module-list.blade.php │ ├── notifications │ └── database-notifications-trigger.blade.php │ ├── modules │ ├── index.blade.php │ └── show.blade.php │ ├── layout │ └── app.blade.php │ └── mail │ └── mentee-accepted.blade.php ├── database ├── .gitignore ├── factories │ ├── TodoFactory.php │ ├── ModuleFactory.php │ ├── TaskFactory.php │ └── Users │ │ ├── UserFactory.php │ │ └── DetailsFactory.php ├── seeders │ ├── DatabaseSeeder.php │ ├── UserSeeder.php │ ├── ModuleAttendanceSeeder.php │ └── CourseSeeder.php └── migrations │ ├── 2024_02_04_213214_create_task_todos_table.php │ ├── 2024_02_04_181323_create_modules_table.php │ ├── 2024_02_04_230237_create_task_progress_todos_table.php │ ├── 2024_02_04_191204_create_user_tokens_table.php │ ├── 2024_02_05_212758_create_users_modules_table.php │ ├── 2024_02_21_013626_create_notifications_table.php │ ├── 2019_08_19_000000_create_failed_jobs_table.php │ ├── 2024_02_04_193809_create_tasks_table.php │ ├── 2014_10_12_000000_create_users_table.php │ ├── 2019_12_14_000001_create_personal_access_tokens_table.php │ ├── 2024_02_04_203555_create_user_tasks_progress_table.php │ └── 2024_02_06_000338_create_user_details_table.php ├── bootstrap ├── cache │ └── .gitignore └── app.php ├── storage ├── logs │ └── .gitignore ├── app │ ├── public │ │ └── .gitignore │ └── .gitignore └── framework │ ├── testing │ └── .gitignore │ ├── views │ └── .gitignore │ ├── cache │ ├── data │ │ └── .gitignore │ └── .gitignore │ ├── sessions │ └── .gitignore │ └── .gitignore ├── app ├── Enums │ ├── Task │ │ ├── TaskActionEnum.php │ │ └── TaskProgressStatusEnum.php │ ├── User │ │ ├── PronounEnum.php │ │ ├── SeniorityEnum.php │ │ ├── JobRoleEnum.php │ │ └── BasedPlaceEnum.php │ └── Module │ │ └── ModuleAttendanceEnum.php ├── Http │ ├── Controllers │ │ ├── LandingController.php │ │ ├── Controller.php │ │ ├── OnboardingController.php │ │ ├── DashboardController.php │ │ ├── ModulesController.php │ │ ├── AuthController.php │ │ └── TasksController.php │ ├── Middleware │ │ ├── EncryptCookies.php │ │ ├── VerifyCsrfToken.php │ │ ├── PreventRequestsDuringMaintenance.php │ │ ├── TrimStrings.php │ │ ├── TrustHosts.php │ │ ├── VerifyOnboarding.php │ │ ├── Authenticate.php │ │ ├── ValidateSignature.php │ │ ├── TrustProxies.php │ │ ├── RedirectIfAuthenticated.php │ │ └── VerifyModuleAttendance.php │ ├── Requests │ │ ├── Modules │ │ │ └── ShowModuleRequest.php │ │ ├── TaskRequest.php │ │ ├── StartTaskRequest.php │ │ └── StoreOnboardRequest.php │ └── Kernel.php ├── Filament │ └── Resources │ │ ├── UserResource │ │ └── Pages │ │ │ ├── CreateUser.php │ │ │ ├── ViewUser.php │ │ │ ├── ListUsers.php │ │ │ └── EditUser.php │ │ ├── ModuleResource │ │ └── Pages │ │ │ ├── CreateModule.php │ │ │ ├── ViewModule.php │ │ │ ├── ListModules.php │ │ │ └── EditModule.php │ │ ├── ModuleAttendanceResource │ │ ├── Pages │ │ │ ├── CreateModuleAttendance.php │ │ │ ├── EditModuleAttendance.php │ │ │ └── ListModuleAttendances.php │ │ └── Actions │ │ │ └── AttendanceApprovalAction.php │ │ ├── ModuleAttendanceResource.php │ │ └── ModuleResource.php ├── Providers │ ├── BroadcastServiceProvider.php │ ├── AppServiceProvider.php │ ├── AuthServiceProvider.php │ ├── EventServiceProvider.php │ ├── RouteServiceProvider.php │ └── Filament │ │ └── AdminPanelProvider.php ├── Models │ ├── Auth │ │ └── Token.php │ ├── Module │ │ ├── Task │ │ │ ├── Todo.php │ │ │ └── Task.php │ │ └── Module.php │ └── Users │ │ ├── ModuleAttendance.php │ │ ├── Details.php │ │ ├── Progress.php │ │ └── User.php ├── Repositories │ └── TaskProgressRepository.php ├── Console │ └── Kernel.php ├── Exceptions │ └── Handler.php ├── Services │ └── TaskService.php └── Mail │ └── MenteeAccepted.php ├── tests ├── TestCase.php ├── Unit │ └── ExampleTest.php ├── Feature │ └── ExampleTest.php └── CreatesApplication.php ├── .gitattributes ├── lang └── pt_BR │ ├── views.php │ ├── pagination.php │ ├── auth.php │ └── passwords.php ├── vite.config.js ├── .gitignore ├── .editorconfig ├── package.json ├── routes ├── channels.php ├── api.php ├── console.php └── web.php ├── config ├── cors.php ├── view.php ├── services.php ├── hashing.php ├── broadcasting.php ├── filesystems.php ├── sanctum.php ├── cache.php ├── queue.php ├── auth.php ├── mail.php ├── logging.php └── database.php ├── phpunit.xml ├── .env.example ├── artisan ├── composer.json └── README.md /public/favicon.ico: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /resources/css/app.css: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /database/.gitignore: -------------------------------------------------------------------------------- 1 | *.sqlite* 2 | -------------------------------------------------------------------------------- /bootstrap/cache/.gitignore: -------------------------------------------------------------------------------- 1 | * 2 | !.gitignore 3 | -------------------------------------------------------------------------------- /resources/js/app.js: -------------------------------------------------------------------------------- 1 | import './bootstrap'; 2 | -------------------------------------------------------------------------------- /storage/logs/.gitignore: -------------------------------------------------------------------------------- 1 | * 2 | !.gitignore 3 | -------------------------------------------------------------------------------- /public/robots.txt: -------------------------------------------------------------------------------- 1 | User-agent: * 2 | Disallow: 3 | -------------------------------------------------------------------------------- /storage/app/public/.gitignore: -------------------------------------------------------------------------------- 1 | * 2 | !.gitignore 3 | -------------------------------------------------------------------------------- /storage/app/.gitignore: -------------------------------------------------------------------------------- 1 | * 2 | !public/ 3 | !.gitignore 4 | -------------------------------------------------------------------------------- /storage/framework/testing/.gitignore: -------------------------------------------------------------------------------- 1 | * 2 | !.gitignore 3 | -------------------------------------------------------------------------------- /storage/framework/views/.gitignore: -------------------------------------------------------------------------------- 1 | * 2 | !.gitignore 3 | -------------------------------------------------------------------------------- /storage/framework/cache/data/.gitignore: -------------------------------------------------------------------------------- 1 | * 2 | !.gitignore 3 | -------------------------------------------------------------------------------- /storage/framework/sessions/.gitignore: -------------------------------------------------------------------------------- 1 | * 2 | !.gitignore 3 | -------------------------------------------------------------------------------- /resources/views/components/mail/text/panel.blade.php: -------------------------------------------------------------------------------- 1 | {{ $slot }} 2 | -------------------------------------------------------------------------------- /resources/views/components/mail/text/table.blade.php: -------------------------------------------------------------------------------- 1 | {{ $slot }} 2 | -------------------------------------------------------------------------------- /storage/framework/cache/.gitignore: -------------------------------------------------------------------------------- 1 | * 2 | !data/ 3 | !.gitignore 4 | -------------------------------------------------------------------------------- /resources/views/components/mail/text/footer.blade.php: -------------------------------------------------------------------------------- 1 | {{ $slot }} 2 | -------------------------------------------------------------------------------- /resources/views/components/mail/text/subcopy.blade.php: -------------------------------------------------------------------------------- 1 | {{ $slot }} 2 | -------------------------------------------------------------------------------- /resources/views/components/mail/text/button.blade.php: -------------------------------------------------------------------------------- 1 | {{ $slot }}: {{ $url }} 2 | -------------------------------------------------------------------------------- /resources/views/components/mail/text/header.blade.php: -------------------------------------------------------------------------------- 1 | [{{ $slot }}]({{ $url }}) 2 | -------------------------------------------------------------------------------- /resources/views/components/mail/html/message.blade.php: -------------------------------------------------------------------------------- 1 | 2 | {{-- Header --}} 3 | 4 | 5 | -------------------------------------------------------------------------------- /resources/views/components/mail/html/table.blade.php: -------------------------------------------------------------------------------- 1 |
2 | {{ Illuminate\Mail\Markdown::parse($slot) }} 3 |
4 | -------------------------------------------------------------------------------- /resources/views/notifications/database-notifications-trigger.blade.php: -------------------------------------------------------------------------------- 1 | 4 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /app/Enums/Task/TaskActionEnum.php: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | {{ Illuminate\Mail\Markdown::parse($slot) }} 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /resources/views/components/mail/text/layout.blade.php: -------------------------------------------------------------------------------- 1 | {!! strip_tags($header ?? '') !!} 2 | 3 | {!! strip_tags($slot) !!} 4 | @isset($subcopy) 5 | 6 | {!! strip_tags($subcopy) !!} 7 | @endisset 8 | 9 | {!! strip_tags($footer ?? '') !!} 10 | -------------------------------------------------------------------------------- /.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 | -------------------------------------------------------------------------------- /app/Enums/User/PronounEnum.php: -------------------------------------------------------------------------------- 1 | 0&&(this.$el.style.height=e+"rem",this.$el.style.height=this.$el.scrollHeight+"px")}}}export{t as default}; 2 | -------------------------------------------------------------------------------- /lang/pt_BR/views.php: -------------------------------------------------------------------------------- 1 | [ 5 | 'labels' => [ 6 | 'onhold' => 'Aguardando aprovação', 7 | 'accepted' => 'Inscrição Ativa', 8 | 'finished' => 'Trilha Finalizada!' 9 | ], 10 | ] 11 | ]; 12 | -------------------------------------------------------------------------------- /app/Http/Controllers/LandingController.php: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 8 | 9 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /.phpunit.cache 2 | /node_modules 3 | /public/build 4 | /public/hot 5 | /public/storage 6 | /storage/*.key 7 | /vendor 8 | .env 9 | .env.backup 10 | .env.production 11 | .phpunit.result.cache 12 | Homestead.json 13 | Homestead.yaml 14 | auth.json 15 | npm-debug.log 16 | yarn-error.log 17 | /.fleet 18 | /.idea 19 | /.vscode 20 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /resources/views/components/mail/html/header.blade.php: -------------------------------------------------------------------------------- 1 | @props(['url']) 2 | 3 | 4 | 5 | @if (trim($slot) === 'Laravel') 6 | 7 | @else 8 | {{ $slot }} 9 | @endif 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /app/Filament/Resources/UserResource/Pages/CreateUser.php: -------------------------------------------------------------------------------- 1 | 13 | */ 14 | protected $except = [ 15 | // 16 | ]; 17 | } 18 | -------------------------------------------------------------------------------- /resources/views/components/mail/html/panel.blade.php: -------------------------------------------------------------------------------- 1 | 2 | 3 | 12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /app/Http/Middleware/VerifyCsrfToken.php: -------------------------------------------------------------------------------- 1 | 13 | */ 14 | protected $except = [ 15 | // 16 | ]; 17 | } 18 | -------------------------------------------------------------------------------- /resources/views/modules/index.blade.php: -------------------------------------------------------------------------------- 1 | @extends('layout.app') 2 | 3 | @section('breadcrumb') 4 | 8 | @endsection 9 | 10 | @section('content') 11 | 12 | 13 | @endsection 14 | -------------------------------------------------------------------------------- /app/Filament/Resources/ModuleAttendanceResource/Pages/CreateModuleAttendance.php: -------------------------------------------------------------------------------- 1 | get('/'); 16 | 17 | $response->assertStatus(200); 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 | -------------------------------------------------------------------------------- /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/Http/Requests/Modules/ShowModuleRequest.php: -------------------------------------------------------------------------------- 1 | user() 13 | ->modules() 14 | ->find($this->route('module')); 15 | } 16 | 17 | public function rules(): array 18 | { 19 | return []; 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /app/Providers/AppServiceProvider.php: -------------------------------------------------------------------------------- 1 | user()->onboarded) { 15 | return $next($request); 16 | } 17 | 18 | return redirect()->route('onboarding'); 19 | 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /app/Http/Middleware/Authenticate.php: -------------------------------------------------------------------------------- 1 | expectsJson() ? null : route('landing'); 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /database/factories/TodoFactory.php: -------------------------------------------------------------------------------- 1 | Task::factory(), 17 | 'description' => $this->faker->sentence() 18 | ]; 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /app/Filament/Resources/UserResource/Pages/ViewUser.php: -------------------------------------------------------------------------------- 1 | belongsTo(User::class); 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "private": true, 3 | "type": "module", 4 | "scripts": { 5 | "dev": "vite", 6 | "build": "vite build" 7 | }, 8 | "devDependencies": { 9 | "@tailwindcss/forms": "^0.5.7", 10 | "@tailwindcss/typography": "^0.5.10", 11 | "autoprefixer": "^10.4.17", 12 | "axios": "^1.6.4", 13 | "laravel-vite-plugin": "^1.0.0", 14 | "postcss": "^8.4.35", 15 | "postcss-nesting": "^12.0.3", 16 | "tailwindcss": "^3.4.1", 17 | "vite": "^5.0.0" 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /app/Filament/Resources/UserResource/Pages/EditUser.php: -------------------------------------------------------------------------------- 1 | call(CourseSeeder::class); 16 | if (config('app.env') == 'local') { 17 | $this->call(UserSeeder::class); 18 | $this->call(ModuleAttendanceSeeder::class); 19 | } 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /app/Filament/Resources/ModuleAttendanceResource/Pages/EditModuleAttendance.php: -------------------------------------------------------------------------------- 1 | $this->faker->sentence(), 16 | 'description' => $this->faker->text(), 17 | 'thumbnail_url' => 'https://http.cat/' . $this->faker->randomElement([200, 201, 420, 404, 500, 422, 301, 302]) 18 | ]; 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /lang/pt_BR/pagination.php: -------------------------------------------------------------------------------- 1 | '« Anterior', 17 | 'next' => 'Próximo »', 18 | 19 | ]; 20 | -------------------------------------------------------------------------------- /routes/channels.php: -------------------------------------------------------------------------------- 1 | id === (int) $id; 18 | }); 19 | -------------------------------------------------------------------------------- /routes/api.php: -------------------------------------------------------------------------------- 1 | get('/user', function (Request $request) { 18 | return $request->user(); 19 | }); 20 | -------------------------------------------------------------------------------- /app/Providers/AuthServiceProvider.php: -------------------------------------------------------------------------------- 1 | 14 | */ 15 | protected $policies = [ 16 | // 17 | ]; 18 | 19 | /** 20 | * Register any authentication / authorization services. 21 | */ 22 | public function boot(): void 23 | { 24 | // 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /routes/console.php: -------------------------------------------------------------------------------- 1 | comment(Inspiring::quote()); 19 | })->purpose('Display an inspiring quote'); 20 | -------------------------------------------------------------------------------- /app/Repositories/TaskProgressRepository.php: -------------------------------------------------------------------------------- 1 | update(['content' => $payload['content']]); 13 | if (isset($payload['todos'])) { 14 | $progress->todos()->sync(array_keys($payload['todos'])); 15 | } 16 | } 17 | 18 | public function setProgress(Progress $progress, TaskProgressStatusEnum $enum): void 19 | { 20 | $progress->update(['status' => $enum->value]); 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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/Http/Controllers/OnboardingController.php: -------------------------------------------------------------------------------- 1 | request()->user(), 15 | ]); 16 | } 17 | 18 | public function postOnboarding(StoreOnboardRequest $request) 19 | { 20 | /** @var User $user */ 21 | $user = request()->user(); 22 | 23 | $user->onboard($request->validated()); 24 | 25 | return redirect()->route('dashboard'); 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /resources/views/components/mail/html/button.blade.php: -------------------------------------------------------------------------------- 1 | @props([ 2 | 'url', 3 | 'color' => 'primary', 4 | 'align' => 'center', 5 | ]) 6 | 7 | 8 | 23 | 24 | 25 | -------------------------------------------------------------------------------- /app/Http/Requests/TaskRequest.php: -------------------------------------------------------------------------------- 1 | progress->status, $enabledStatuses); 18 | } 19 | 20 | public function rules(): array 21 | { 22 | return [ 23 | 'content' => ['nullable', 'string'], 24 | 'todos' => ['nullable', 'array'] 25 | ]; 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /app/Models/Module/Task/Todo.php: -------------------------------------------------------------------------------- 1 | belongsTo(Task::class); 24 | } 25 | 26 | protected static function newFactory(): TodoFactory 27 | { 28 | return TodoFactory::new(); 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /app/Enums/Module/ModuleAttendanceEnum.php: -------------------------------------------------------------------------------- 1 | value); 18 | } 19 | 20 | public function getColor(): string|array|null 21 | { 22 | return match($this) { 23 | self::ON_HOLD => 'warning', 24 | self::ACCEPTED => 'info', 25 | self::FINISHED => 'success', 26 | }; 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /lang/pt_BR/auth.php: -------------------------------------------------------------------------------- 1 | 'Essas credenciais não foram encontradas em nossos registros.', 17 | 'password' => 'A senha informada está incorreta.', 18 | 'throttle' => 'Muitas tentativas de login. Tente novamente em :seconds segundos.', 19 | 20 | ]; 21 | -------------------------------------------------------------------------------- /app/Exceptions/Handler.php: -------------------------------------------------------------------------------- 1 | 14 | */ 15 | protected $dontFlash = [ 16 | 'current_password', 17 | 'password', 18 | 'password_confirmation', 19 | ]; 20 | 21 | /** 22 | * Register the exception handling callbacks for the application. 23 | */ 24 | public function register(): void 25 | { 26 | $this->reportable(function (Throwable $e) { 27 | // 28 | }); 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /resources/views/components/mail/text/message.blade.php: -------------------------------------------------------------------------------- 1 | 2 | {{-- Header --}} 3 | 4 | 5 | {{ config('app.name') }} 6 | 7 | 8 | 9 | {{-- Body --}} 10 | {{ $slot }} 11 | 12 | {{-- Subcopy --}} 13 | @isset($subcopy) 14 | 15 | 16 | {{ $subcopy }} 17 | 18 | 19 | @endisset 20 | 21 | {{-- Footer --}} 22 | 23 | 24 | © {{ date('Y') }} {{ config('app.name') }}. @lang('All rights reserved.') 25 | 26 | 27 | 28 | -------------------------------------------------------------------------------- /database/seeders/UserSeeder.php: -------------------------------------------------------------------------------- 1 | has(Details::factory(), 'details') 16 | ->onboarded() 17 | ->create([ 18 | 'name' => 'Daniel Reis', 19 | 'email' => 'idanielreiss@gmail.com', 20 | 'github_id' => 6912596, 21 | 'github_username' => 'danielhe4rt', 22 | 'description' => 'Just another developer', 23 | 'password' => Hash::make('secret123'), 24 | ]); 25 | 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /database/migrations/2024_02_04_213214_create_task_todos_table.php: -------------------------------------------------------------------------------- 1 | id(); 16 | $table->foreignId('task_id')->constrained('tasks'); 17 | $table->string('description'); 18 | $table->timestamps(); 19 | }); 20 | } 21 | 22 | /** 23 | * Reverse the migrations. 24 | */ 25 | public function down(): void 26 | { 27 | Schema::dropIfExists('task_todos'); 28 | } 29 | }; 30 | -------------------------------------------------------------------------------- /database/migrations/2024_02_04_181323_create_modules_table.php: -------------------------------------------------------------------------------- 1 | id(); 16 | $table->string('name'); 17 | $table->text('description'); 18 | $table->string('thumbnail_url'); 19 | $table->timestamps(); 20 | }); 21 | } 22 | 23 | /** 24 | * Reverse the migrations. 25 | */ 26 | public function down(): void 27 | { 28 | Schema::dropIfExists('modules'); 29 | } 30 | }; 31 | -------------------------------------------------------------------------------- /database/factories/TaskFactory.php: -------------------------------------------------------------------------------- 1 | Module::factory(), 17 | 'title' => $this->faker->sentence(), 18 | 'thumbnail_url' => $this->faker->imageUrl(), 19 | 'description' => $this->faker->text(), 20 | 'deadline' => now(), 21 | 'tips' => [ 22 | $this->faker->sentence(), 23 | $this->faker->sentence(), 24 | $this->faker->sentence(), 25 | ] 26 | ]; 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /public/js/filament/forms/components/tags-input.js: -------------------------------------------------------------------------------- 1 | function i({state:a,splitKeys:n}){return{newTag:"",state:a,createTag:function(){if(this.newTag=this.newTag.trim(),this.newTag!==""){if(this.state.includes(this.newTag)){this.newTag="";return}this.state.push(this.newTag),this.newTag=""}},deleteTag:function(t){this.state=this.state.filter(e=>e!==t)},reorderTags:function(t){let e=this.state.splice(t.oldIndex,1)[0];this.state.splice(t.newIndex,0,e),this.state=[...this.state]},input:{["x-on:blur"]:"createTag()",["x-model"]:"newTag",["x-on:keydown"](t){["Enter",...n].includes(t.key)&&(t.preventDefault(),t.stopPropagation(),this.createTag())},["x-on:paste"](){this.$nextTick(()=>{if(n.length===0){this.createTag();return}let t=n.map(e=>e.replace(/[/\-\\^$*+?.()|[\]{}]/g,"\\$&")).join("|");this.newTag.split(new RegExp(t,"g")).forEach(e=>{this.newTag=e,this.createTag()})})}}}}export{i as default}; 2 | -------------------------------------------------------------------------------- /database/migrations/2024_02_04_230237_create_task_progress_todos_table.php: -------------------------------------------------------------------------------- 1 | foreignId('task_progress_id')->constrained('user_tasks_progress'); 16 | $table->foreignId('task_todo_id')->constrained('task_todos'); 17 | $table->timestamps(); 18 | }); 19 | } 20 | 21 | /** 22 | * Reverse the migrations. 23 | */ 24 | public function down(): void 25 | { 26 | Schema::dropIfExists('task_progress_todos'); 27 | } 28 | }; 29 | -------------------------------------------------------------------------------- /database/migrations/2024_02_04_191204_create_user_tokens_table.php: -------------------------------------------------------------------------------- 1 | id(); 16 | $table->foreignId('user_id')->constrained('users'); 17 | $table->string('access_token'); 18 | $table->string('refresh_token'); 19 | $table->timestamps(); 20 | }); 21 | } 22 | 23 | /** 24 | * Reverse the migrations. 25 | */ 26 | public function down(): void 27 | { 28 | Schema::dropIfExists('user_tokens'); 29 | } 30 | }; 31 | -------------------------------------------------------------------------------- /app/Enums/User/JobRoleEnum.php: -------------------------------------------------------------------------------- 1 | belongsTo(User::class); 18 | } 19 | 20 | public function module(): BelongsTo 21 | { 22 | return $this->belongsTo(Module::class); 23 | } 24 | 25 | public function accept(): void 26 | { 27 | $this->update(['status' => ModuleAttendanceEnum::ACCEPTED]); 28 | } 29 | 30 | protected $casts = [ 31 | 'status' => ModuleAttendanceEnum::class 32 | ]; 33 | } 34 | -------------------------------------------------------------------------------- /database/migrations/2024_02_05_212758_create_users_modules_table.php: -------------------------------------------------------------------------------- 1 | id(); 16 | $table->foreignId('module_id')->constrained('modules'); 17 | $table->foreignId('user_id')->constrained('users'); 18 | $table->string('status')->default('onhold'); 19 | $table->timestamps(); 20 | }); 21 | } 22 | 23 | /** 24 | * Reverse the migrations. 25 | */ 26 | public function down(): void 27 | { 28 | Schema::dropIfExists('users_modules'); 29 | } 30 | }; 31 | -------------------------------------------------------------------------------- /database/migrations/2024_02_21_013626_create_notifications_table.php: -------------------------------------------------------------------------------- 1 | uuid('id')->primary(); 16 | $table->string('type'); 17 | $table->morphs('notifiable'); 18 | $table->text('data'); 19 | $table->timestamp('read_at')->nullable(); 20 | $table->timestamps(); 21 | }); 22 | } 23 | 24 | /** 25 | * Reverse the migrations. 26 | */ 27 | public function down(): void 28 | { 29 | Schema::dropIfExists('notifications'); 30 | } 31 | }; 32 | -------------------------------------------------------------------------------- /app/Http/Middleware/RedirectIfAuthenticated.php: -------------------------------------------------------------------------------- 1 | check()) { 24 | return redirect()->route('module.index'); 25 | } 26 | } 27 | 28 | return $next($request); 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /app/Enums/Task/TaskProgressStatusEnum.php: -------------------------------------------------------------------------------- 1 | "Não Enviado", 18 | self::Submitted => "Aguardando Revisão", 19 | self::Completed => "Atividade Aceita", 20 | self::Need_Improvements => 'Aguardando melhorias', 21 | }; 22 | } 23 | 24 | public function getLabel(): string 25 | { 26 | return match ($this) { 27 | self::Started => 'secondary', 28 | self::Submitted => 'warning', 29 | self::Need_Improvements => 'info', 30 | self::Completed => 'success', 31 | }; 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /lang/pt_BR/passwords.php: -------------------------------------------------------------------------------- 1 | 'A senha e a confirmação devem combinar e possuir pelo menos seis caracteres.', 17 | 'reset' => 'Sua senha foi redefinida!', 18 | 'sent' => 'Enviamos seu link de redefinição de senha por e-mail!', 19 | 'throttled' => 'Aguarde antes de tentar novamente.', 20 | 'token' => 'Este token de redefinição de senha é inválido.', 21 | 'user' => 'Não encontramos um usuário com esse endereço de e-mail.', 22 | 23 | ]; 24 | -------------------------------------------------------------------------------- /public/js/filament/forms/components/key-value.js: -------------------------------------------------------------------------------- 1 | function r({state:i}){return{state:i,rows:[],shouldUpdateRows:!0,init:function(){this.updateRows(),this.rows.length<=0?this.rows.push({key:"",value:""}):this.updateState(),this.$watch("state",(t,e)=>{let s=o=>o===null?0:Array.isArray(o)?o.length:typeof o!="object"?0:Object.keys(o).length;s(t)===0&&s(e)===0||this.updateRows()})},addRow:function(){this.rows.push({key:"",value:""}),this.updateState()},deleteRow:function(t){this.rows.splice(t,1),this.rows.length<=0&&this.addRow(),this.updateState()},reorderRows:function(t){let e=Alpine.raw(this.rows),s=e.splice(t.oldIndex,1)[0];e.splice(t.newIndex,0,s),this.rows=e,this.updateState()},updateRows:function(){if(!this.shouldUpdateRows){this.shouldUpdateRows=!0;return}let t=[];for(let[e,s]of Object.entries(this.state??{}))t.push({key:e,value:s});this.rows=t},updateState:function(){let t={};this.rows.forEach(e=>{e.key===""||e.key===null||(t[e.key]=e.value)}),this.shouldUpdateRows=!1,this.state=t}}}export{r as default}; 2 | -------------------------------------------------------------------------------- /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/Enums/User/BasedPlaceEnum.php: -------------------------------------------------------------------------------- 1 | user(); 13 | $completedTasks = $user->tasks()->where('status', TaskProgressStatusEnum::Completed)->count(); 14 | $activeTask = $user->tasks()->whereIn('status', [ 15 | TaskProgressStatusEnum::Started, 16 | TaskProgressStatusEnum::Need_Improvements, 17 | ])->first(); 18 | $moduleAcceptance = $user->modules()->latest()->first()?->pivot; 19 | 20 | return view('dashboard', [ 21 | 'user' => $user, 22 | 'details' => $user->details, 23 | 'completedTasks' => $completedTasks, 24 | 'activeTask' => $activeTask, 25 | 'moduleAcceptance' => $moduleAcceptance, 26 | ]); 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /database/migrations/2024_02_04_193809_create_tasks_table.php: -------------------------------------------------------------------------------- 1 | id(); 16 | $table->foreignId('module_id')->constrained('modules'); 17 | $table->string('title'); 18 | $table->string('thumbnail_url'); 19 | $table->string('description'); 20 | $table->timestamp('deadline'); 21 | $table->tinyInteger('order'); 22 | $table->text('tips')->nullable(); 23 | $table->timestamps(); 24 | }); 25 | } 26 | 27 | /** 28 | * Reverse the migrations. 29 | */ 30 | public function down(): void 31 | { 32 | Schema::dropIfExists('tasks'); 33 | } 34 | }; 35 | -------------------------------------------------------------------------------- /database/migrations/2014_10_12_000000_create_users_table.php: -------------------------------------------------------------------------------- 1 | id(); 16 | $table->string('name'); 17 | $table->string('github_id')->unique(); 18 | $table->string('github_username'); 19 | $table->string('description'); 20 | $table->string('email')->unique(); 21 | $table->string('onboarded')->default(false); 22 | $table->string('password')->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 | -------------------------------------------------------------------------------- /app/Filament/Resources/ModuleAttendanceResource/Pages/ListModuleAttendances.php: -------------------------------------------------------------------------------- 1 | Tab::make('All')]; 25 | 26 | foreach (ModuleAttendanceEnum::cases() as $attendanceEnum) { 27 | $tabs[$attendanceEnum->value] = Tab::make()->query(fn($query) => $query->where('status', $attendanceEnum->value)); 28 | } 29 | 30 | return $tabs; 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /app/Models/Module/Task/Task.php: -------------------------------------------------------------------------------- 1 | 'array' 28 | ]; 29 | 30 | public function todos(): HasMany 31 | { 32 | return $this->hasMany(Todo::class); 33 | } 34 | 35 | public function progress(): HasMany 36 | { 37 | return $this->hasMany(Progress::class); 38 | } 39 | 40 | protected static function newFactory(): TaskFactory 41 | { 42 | return TaskFactory::new(); 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /database/seeders/ModuleAttendanceSeeder.php: -------------------------------------------------------------------------------- 1 | count(2)->create(); 17 | $users = User::factory()->has(Details::factory(), 'details') 18 | ->count(10) 19 | ->create(); 20 | 21 | $users->each(function (User $user) { 22 | // Since it has a previously "production" module, I decided to add two new modules to testing purposes. 23 | $moduleId = rand(1, 3); 24 | /** @var ModuleAttendanceEnum $statusCases */ 25 | $statusCases = collect(ModuleAttendanceEnum::cases())->shuffle()->first(); 26 | 27 | $user->modules()->attach($moduleId, ['status' => $statusCases]); 28 | }); 29 | 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /database/migrations/2024_02_04_203555_create_user_tasks_progress_table.php: -------------------------------------------------------------------------------- 1 | id(); 16 | $table->foreignId('user_id')->constrained('users'); 17 | $table->foreignId('task_id')->constrained('tasks'); 18 | $table->string('status')->default('started'); 19 | $table->text('content')->nullable(); 20 | $table->text('feedback')->nullable(); 21 | $table->timestamp('delivered_at')->nullable(); 22 | $table->timestamps(); 23 | }); 24 | } 25 | 26 | /** 27 | * Reverse the migrations. 28 | */ 29 | public function down(): void 30 | { 31 | Schema::dropIfExists('user_tasks_progress'); 32 | } 33 | }; 34 | -------------------------------------------------------------------------------- /app/Http/Controllers/ModulesController.php: -------------------------------------------------------------------------------- 1 | paginate(); 14 | 15 | return view('modules.index', [ 16 | 'modules' => $modules, 17 | 'userModules' => auth()->user()->modules 18 | ]); 19 | } 20 | 21 | public function getModule(Module $module): View 22 | { 23 | $taskList = $module->tasks()->get(); 24 | $userTasks = request()->user()->tasks; 25 | 26 | return view('modules.show', [ 27 | 'tasks' => $taskList, 28 | 'userTasks' => $userTasks, 29 | 'module' => $module 30 | ]); 31 | } 32 | 33 | public function postInitModule(Module $module): RedirectResponse 34 | { 35 | $module->users()->attach(request()->user()->id); 36 | 37 | return redirect()->route('modules.show', $module); 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /app/Models/Users/Details.php: -------------------------------------------------------------------------------- 1 | 'boolean', 34 | 'switching_career' => 'boolean', 35 | 'pronouns' => PronounEnum::class, 36 | 'seniority' => SeniorityEnum::class, 37 | ]; 38 | 39 | public function user(): BelongsTo 40 | { 41 | return $this->belongsTo(User::class); 42 | } 43 | 44 | 45 | 46 | } 47 | -------------------------------------------------------------------------------- /app/Http/Requests/StartTaskRequest.php: -------------------------------------------------------------------------------- 1 | route('task'); 16 | 17 | /** @var Module $task */ 18 | $module = $this->route('module'); 19 | 20 | if ($task->order == 1) { 21 | return true; 22 | } 23 | 24 | $previousTaskOrder = $task->order - 1; 25 | $previousTask = Task::query() 26 | ->where([ 27 | 'order' => $previousTaskOrder, 28 | 'module_id' => $module->id 29 | ])->first(); 30 | 31 | $finishedPreviousTask = Progress::query()->where([ 32 | 'user_id' => request()->user()->id, 33 | 'task_id' => $previousTask->id, 34 | 'status' => 'completed' 35 | ])->exists(); 36 | 37 | return $finishedPreviousTask; 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /app/Providers/EventServiceProvider.php: -------------------------------------------------------------------------------- 1 | > 18 | */ 19 | protected $listen = [ 20 | Registered::class => [ 21 | SendEmailVerificationNotification::class, 22 | ], 23 | ]; 24 | 25 | /** 26 | * Register any events for your application. 27 | */ 28 | public function boot(): void 29 | { 30 | // 31 | } 32 | 33 | /** 34 | * Determine if events and listeners should be automatically discovered. 35 | */ 36 | public function shouldDiscoverEvents(): bool 37 | { 38 | return false; 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /database/factories/Users/UserFactory.php: -------------------------------------------------------------------------------- 1 | 10 | */ 11 | class UserFactory extends Factory 12 | { 13 | 14 | protected static ?string $password; 15 | 16 | protected $model = User::class; 17 | 18 | public function definition(): array 19 | { 20 | return [ 21 | 'name' => $this->faker->name, 22 | 'email' => $this->faker->email, 23 | 'github_id' => rand(11111, 99999), 24 | 'github_username' => $this->faker->userName(), 25 | 'description' => $this->faker->sentence(), 26 | 'onboarded' => false, 27 | 'password' => null, 28 | ]; 29 | } 30 | 31 | /** 32 | * Indicate that the model's email address should be unverified. 33 | */ 34 | public function onboarded(): static 35 | { 36 | return $this->state(fn(array $attributes) => [ 37 | 'onboarded' => true, 38 | ]); 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /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/views/layout/app.blade.php: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 7 | 8 | {{ config('app.name') }} 9 | 10 | 11 | 12 | @vite(['resources/js/app.js']) 13 | 14 | 15 | 16 |
17 |
18 | @yield("breadcrumb", '') 19 |
20 | @yield('content') 21 | 22 | 23 | 24 | 25 | 26 | 27 | @yield('script', '') 28 |
29 | 30 | 31 | -------------------------------------------------------------------------------- /app/Models/Module/Module.php: -------------------------------------------------------------------------------- 1 | hasMany(Task::class); 27 | } 28 | 29 | public function users(): BelongsToMany 30 | { 31 | return $this->belongsToMany( 32 | User::class, 33 | 'users_modules', 34 | 'module_id', 35 | 'user_id' 36 | )->using(ModuleAttendance::class) 37 | ->withPivot('status') 38 | ->withTimestamps(); 39 | } 40 | 41 | protected static function newFactory(): ModuleFactory 42 | { 43 | return ModuleFactory::new(); 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /resources/views/components/footer.blade.php: -------------------------------------------------------------------------------- 1 |
2 |
3 |
4 | 5 | Logo 6 | 7 | © {{ now()->year }} Daniel Reis 8 |
9 | 10 | 16 |
17 |
18 | -------------------------------------------------------------------------------- /phpunit.xml: -------------------------------------------------------------------------------- 1 | 2 | 7 | 8 | 9 | tests/Unit 10 | 11 | 12 | tests/Feature 13 | 14 | 15 | 16 | 17 | app 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | -------------------------------------------------------------------------------- /database/factories/Users/DetailsFactory.php: -------------------------------------------------------------------------------- 1 | faker->userName(); 20 | return [ 21 | 'user_id' => User::factory(), 22 | 'company' => $this->faker->company(), 23 | 'role' => $this->faker->randomElement(JobRoleEnum::cases()), 24 | 'seniority' => $this->faker->randomElement(SeniorityEnum::cases()), 25 | 'based_in' => $this->faker->randomElement(BasedPlaceEnum::cases()), 26 | 'pronouns' => $this->faker->randomElement(PronounEnum::cases()), 27 | 'twitter_handle' => $handle, 28 | 'devto_handle' => $handle, 29 | 'comments' => $this->faker->sentence(), 30 | 'is_employed' => false, 31 | 'switching_career' => false, 32 | ]; 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /app/Providers/RouteServiceProvider.php: -------------------------------------------------------------------------------- 1 | by($request->user()?->id ?: $request->ip()); 29 | }); 30 | 31 | $this->routes(function () { 32 | Route::middleware('api') 33 | ->prefix('api') 34 | ->group(base_path('routes/api.php')); 35 | 36 | Route::middleware('web') 37 | ->group(base_path('routes/web.php')); 38 | }); 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /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 | 'github' => [ 35 | 'client_id' => env('GITHUB_CLIENT_ID'), 36 | 'client_secret' => env('GITHUB_CLIENT_SECRET'), 37 | 'redirect' => env('GITHUB_CLIENT_REDIRECT'), 38 | ], 39 | 40 | ]; 41 | -------------------------------------------------------------------------------- /app/Http/Middleware/VerifyModuleAttendance.php: -------------------------------------------------------------------------------- 1 | module; 21 | $module = Module::find($module->id ?? $module); 22 | 23 | if (!$userModule = $module->users()->find(auth()->user()->getKey())) { 24 | return $this->redirectWithErrorMessage('Boa pergunta'); 25 | } 26 | 27 | return match ($userModule->pivot->status) { 28 | ModuleAttendanceEnum::ACCEPTED, ModuleAttendanceEnum::FINISHED => $next($request), 29 | ModuleAttendanceEnum::ON_HOLD => $this->redirectWithErrorMessage('Caraio') 30 | }; 31 | 32 | } 33 | 34 | private function redirectWithErrorMessage(string $errorMessage): RedirectResponse 35 | { 36 | return redirect() 37 | ->route('module.index') 38 | ->withErrors($errorMessage); 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /database/migrations/2024_02_06_000338_create_user_details_table.php: -------------------------------------------------------------------------------- 1 | id(); 16 | $table->foreignId('user_id')->constrained(); 17 | $table->string('company')->nullable(); 18 | $table->string('role')->nullable(); 19 | $table->string('seniority')->nullable(); 20 | $table->string('based_in')->nullable(); 21 | $table->string('pronouns')->nullable(); 22 | $table->boolean('switching_career')->nullable(); 23 | $table->string('twitter_handle')->nullable(); 24 | $table->string('devto_handle')->nullable(); 25 | $table->text('comments')->nullable(); 26 | $table->boolean('is_employed')->nullable(); 27 | $table->timestamps(); 28 | }); 29 | } 30 | 31 | /** 32 | * Reverse the migrations. 33 | */ 34 | public function down(): void 35 | { 36 | Schema::dropIfExists('user_details'); 37 | } 38 | }; 39 | -------------------------------------------------------------------------------- /app/Http/Requests/StoreOnboardRequest.php: -------------------------------------------------------------------------------- 1 | ['nullable', 'string'], 19 | 'role' => ['required', new Enum(JobRoleEnum::class)], 20 | 'seniority' => ['required', new Enum(SeniorityEnum::class)], 21 | 'based_in' => ['required', new Enum(BasedPlaceEnum::class)], 22 | 'pronouns' => ['required', new Enum(PronounEnum::class)], 23 | 'twitter_handle' => ['required', 'string'], 24 | 'devto_handle' => ['required', 'string'], 25 | 'comments' => ['nullable', 'string'], 26 | 'is_employed' => ['required', 'boolean'], 27 | 'switching_career' => ['required', 'boolean'] 28 | ]; 29 | } 30 | 31 | public function prepareForValidation(): void 32 | { 33 | $this->merge(['is_employed' => $this->has('is_employed')]); 34 | $this->merge(['switching_career' => $this->has('switching_career')]); 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /app/Models/Users/Progress.php: -------------------------------------------------------------------------------- 1 | 'datetime', 28 | 'status' => TaskProgressStatusEnum::class, 29 | ]; 30 | 31 | public function user(): BelongsTo 32 | { 33 | return $this->belongsTo(User::class); 34 | } 35 | 36 | public function task(): BelongsTo 37 | { 38 | return $this->belongsTo(Task::class); 39 | } 40 | 41 | public function attendantsCount(): int 42 | { 43 | return $this->where('task_id', $this->attributes['task_id'])->count(); 44 | } 45 | 46 | public function todos(): BelongsToMany 47 | { 48 | return $this->belongsToMany( 49 | Todo::class, 50 | 'task_progress_todos', 51 | 'task_progress_id', 52 | 'task_todo_id' 53 | )->withTimestamps(); 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /.env.example: -------------------------------------------------------------------------------- 1 | APP_NAME=Laravel 2 | APP_ENV=local 3 | APP_KEY= 4 | APP_DEBUG=true 5 | APP_URL=http://localhost 6 | 7 | LOG_CHANNEL=stack 8 | LOG_DEPRECATIONS_CHANNEL=null 9 | LOG_LEVEL=debug 10 | 11 | DB_CONNECTION=mysql 12 | DB_HOST=127.0.0.1 13 | DB_PORT=3306 14 | DB_DATABASE=laravel 15 | DB_USERNAME=root 16 | DB_PASSWORD= 17 | 18 | BROADCAST_DRIVER=log 19 | CACHE_DRIVER=file 20 | FILESYSTEM_DISK=local 21 | QUEUE_CONNECTION=sync 22 | SESSION_DRIVER=file 23 | SESSION_LIFETIME=120 24 | 25 | MEMCACHED_HOST=127.0.0.1 26 | 27 | REDIS_HOST=127.0.0.1 28 | REDIS_PASSWORD=null 29 | REDIS_PORT=6379 30 | 31 | MAIL_MAILER=smtp 32 | MAIL_HOST=mailpit 33 | MAIL_PORT=1025 34 | MAIL_USERNAME=null 35 | MAIL_PASSWORD=null 36 | MAIL_ENCRYPTION=null 37 | MAIL_FROM_ADDRESS="hello@example.com" 38 | MAIL_FROM_NAME="${APP_NAME}" 39 | 40 | AWS_ACCESS_KEY_ID= 41 | AWS_SECRET_ACCESS_KEY= 42 | AWS_DEFAULT_REGION=us-east-1 43 | AWS_BUCKET= 44 | AWS_USE_PATH_STYLE_ENDPOINT=false 45 | 46 | PUSHER_APP_ID= 47 | PUSHER_APP_KEY= 48 | PUSHER_APP_SECRET= 49 | PUSHER_HOST= 50 | PUSHER_PORT=443 51 | PUSHER_SCHEME=https 52 | PUSHER_APP_CLUSTER=mt1 53 | 54 | VITE_APP_NAME="${APP_NAME}" 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 | 61 | GITHUB_CLIENT_ID="your-client-id" 62 | GITHUB_CLIENT_SECRET="your-client-secret" 63 | GITHUB_CLIENT_REDIRECT="http://localhost:8000/oauth/github/callback" 64 | -------------------------------------------------------------------------------- /resources/views/components/mail/html/layout.blade.php: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | {{ config('app.name') }} 5 | 6 | 7 | 8 | 9 | 26 | 27 | 28 | 29 | 30 | 31 | 54 | 55 | 56 | 57 | 58 | -------------------------------------------------------------------------------- /resources/views/mail/mentee-accepted.blade.php: -------------------------------------------------------------------------------- 1 | 2 | {{-- Greeting --}} 3 | @if (! empty($greeting)) 4 | # {{ $greeting }} 5 | @else 6 | @if ($level === 'error') 7 | # @lang('Whoops!') 8 | @else 9 | # @lang('Hello!') 10 | @endif 11 | @endif 12 | 13 | {{-- Intro Lines --}} 14 | @foreach ($introLines as $line) 15 | {{ $line }} 16 | 17 | @endforeach 18 | 19 | {{-- Action Button --}} 20 | @isset($actionText) 21 | $level, 24 | default => 'primary', 25 | }; 26 | ?> 27 | 28 | {{ $actionText }} 29 | 30 | @endisset 31 | 32 | {{-- Outro Lines --}} 33 | @foreach ($outroLines as $line) 34 | {{ $line }} 35 | 36 | @endforeach 37 | 38 | {{-- Salutation --}} 39 | @if (! empty($salutation)) 40 | {{ $salutation }} 41 | @else 42 | @lang('Regards'),
43 | {{ config('app.name') }} 44 | @endif 45 | 46 | {{-- Subcopy --}} 47 | @isset($actionText) 48 | 49 | @lang( 50 | "If you're having trouble clicking the \":actionText\" button, copy and paste the URL below\n". 51 | 'into your web browser:', 52 | [ 53 | 'actionText' => $actionText, 54 | ] 55 | ) [{{ $displayableActionUrl }}]({{ $actionUrl }}) 56 | 57 | @endisset 58 |
59 | -------------------------------------------------------------------------------- /app/Services/TaskService.php: -------------------------------------------------------------------------------- 1 | title('Rascunho salvo') 21 | ->info() 22 | ->body(sprintf('O %s salvou um rascunho, deseja visualizar?', $progress->user->name)) 23 | ->actions([ 24 | Action::make('Visualizar',) 25 | ->button() 26 | ->url('https://google.com'), 27 | ]) 28 | ->sendToDatabase(auth()->user()); 29 | 30 | $this->repository->updateTask($progress, $payload); 31 | $this->repository->setProgress($progress, TaskProgressStatusEnum::Started); 32 | } 33 | 34 | public function sendTaskForReview(Progress $progress, array $payload): void 35 | { 36 | Notification::make() 37 | ->title('Submissão Recebida') 38 | ->warning() 39 | ->body(sprintf('Mentorado %s submeteu a tarefa, deseja visualizar?', $progress->user->name)) 40 | ->actions([ 41 | Action::make('Visualizar') 42 | ->button() 43 | ->url('https://google.com'), 44 | ]) 45 | ->sendToDatabase(auth()->user()); 46 | $this->repository->updateTask($progress, $payload); 47 | $this->repository->setProgress($progress, TaskProgressStatusEnum::Submitted); 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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', 12), 33 | 'verify' => true, 34 | ], 35 | 36 | /* 37 | |-------------------------------------------------------------------------- 38 | | Argon Options 39 | |-------------------------------------------------------------------------- 40 | | 41 | | Here you may specify the configuration options that should be used when 42 | | passwords are hashed using the Argon algorithm. These will allow you 43 | | to control the amount of time it takes to hash the given password. 44 | | 45 | */ 46 | 47 | 'argon' => [ 48 | 'memory' => 65536, 49 | 'threads' => 1, 50 | 'time' => 4, 51 | 'verify' => true, 52 | ], 53 | 54 | ]; 55 | -------------------------------------------------------------------------------- /resources/views/modules/show.blade.php: -------------------------------------------------------------------------------- 1 | @extends('layout.app') 2 | 3 | @section('breadcrumb') 4 | 9 | @endsection 10 | 11 | @section('content') 12 | 13 |
14 |
15 | ... 16 |
17 |
18 |
19 | 20 | : {{ $module->users()->count() }} 21 | 22 | 23 | : {{ $module->users()->where('status', 'finished')->count() }} 24 | 25 |
26 |
27 |
28 |

29 | {{ $module->name }} 30 |

31 |

32 | {{ $module->description }} 33 |

34 | 35 | : {{ $module->created_at->format('d/m/Y') }} 36 | 37 |
38 |
39 |
40 |
41 | 42 | 43 | @endsection 44 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /app/Http/Controllers/AuthController.php: -------------------------------------------------------------------------------- 1 | json(['sai daqui' => 'caraio']); 17 | } 18 | 19 | return Socialite::driver($provider) 20 | ->scopes(['user:email', 'user']) 21 | ->redirect(); 22 | } 23 | 24 | public function getAuthenticate(string $provider): RedirectResponse|JsonResponse 25 | { 26 | if ($provider !== "github") { 27 | return response()->json(['sai daqui' => 'caraio']); 28 | } 29 | 30 | $providerUser = Socialite::driver($provider)->user(); 31 | 32 | 33 | /** @var User $user */ 34 | $user = User::updateOrCreate([ 35 | 'github_id' => $providerUser->getId(), 36 | ], [ 37 | 'github_username' => $providerUser->getNickname(), 38 | 'name' => $providerUser->getName(), 39 | 'email' => $providerUser->getEmail(), 40 | 'description' => $providerUser->user['bio'] ?? '404 Description Not Found' 41 | ]); 42 | 43 | $user->tokens()->create([ 44 | 'access_token' => $providerUser->token, 45 | 'refresh_token' => $providerUser->refreshToken, 46 | ]); 47 | 48 | Auth::login($user); 49 | 50 | return response()->redirectToRoute('module.index'); 51 | } 52 | 53 | public function postLogout(): RedirectResponse 54 | { 55 | auth()->logout(); 56 | 57 | return redirect()->route('landing'); 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /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/Filament/Resources/ModuleAttendanceResource/Actions/AttendanceApprovalAction.php: -------------------------------------------------------------------------------- 1 | visible(fn ($record) => $record->status == ModuleAttendanceEnum::ON_HOLD); 30 | 31 | $this->color('success'); 32 | $this->icon(FilamentIcon::resolve('actions::edit-action') ?? 'heroicon-m-pencil-square'); 33 | $this->successNotificationTitle(__('filament-actions::edit.single.notifications.saved.title')); 34 | 35 | $this->action(function (): void { 36 | $this->process(function (array $data, ModuleAttendance $record, Table $table) { 37 | $deniedStatuses = [ 38 | ModuleAttendanceEnum::ACCEPTED, 39 | ModuleAttendanceEnum::FINISHED 40 | ]; 41 | 42 | if (in_array($record->status, $deniedStatuses)) { 43 | return; 44 | } 45 | 46 | $record->accept(); 47 | Mail::to('idanielreiss@gmail.com')->send(new MenteeAccepted( 48 | module: $record->module, 49 | user: $record->user 50 | )); 51 | 52 | $this->success(); 53 | }); 54 | }); 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /app/Mail/MenteeAccepted.php: -------------------------------------------------------------------------------- 1 | subject('Mentorias Fodas') 49 | ->line(sprintf('Salve %s, tudo tranquilo? Espero que sim!', $this->user->name)) 50 | ->line('Se você está recebendo esse e-mail, significa que você foi aprovado/a em nossa mentoria!') 51 | ->line(sprintf('A trilha selecionada foi **%s**, e você deverá concluí-la na maior calma do mundo!', $this->module->name)) 52 | ->line('Como isso é um processo de aprimoramento, eu gostaria MUITO que você fizesse as tarefas utilizando os meios comuns de estudo.') 53 | ->action('Começar Trilha', route('module.index')) 54 | ->render() 55 | ); 56 | } 57 | 58 | /** 59 | * Get the attachments for the message. 60 | * 61 | * @return array 62 | */ 63 | public function attachments(): array 64 | { 65 | return []; 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /app/Http/Controllers/TasksController.php: -------------------------------------------------------------------------------- 1 | $taskProgress, 23 | 'module' => $module 24 | ]); 25 | } 26 | 27 | public function postInitTask(StartTaskRequest $request, Module $module, Task $task) 28 | { 29 | $progress = auth() 30 | ->user() 31 | ->tasks() 32 | ->where(['task_id' => $task->id]) 33 | ->first(); 34 | 35 | if ($progress) { 36 | return redirect()->route('tasks.show', ['module' => $module->id, 'taskProgress' => $progress->id]); 37 | } 38 | 39 | $progress = auth() 40 | ->user() 41 | ->tasks() 42 | ->create([ 43 | 'task_id' => $task->getKey() 44 | ]); 45 | 46 | return redirect()->route('tasks.show', ['module' => $module->id, 'taskProgress' => $progress->id]); 47 | } 48 | 49 | public function postTaskAction( 50 | TaskRequest $request, 51 | Module $module, 52 | Progress $progress, 53 | TaskActionEnum $action 54 | ) 55 | { 56 | $payload = $request->validated(); 57 | 58 | match ($action) { 59 | TaskActionEnum::Draft => $this->service->updateDraftTask($progress, $payload), 60 | TaskActionEnum::Submit => $this->service->sendTaskForReview($progress, $payload), 61 | }; 62 | 63 | return redirect()->route('tasks.show', [ 64 | 'module' => $module, 65 | 'taskProgress' => $progress, 66 | ]); 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /public/js/filament/tables/components/table.js: -------------------------------------------------------------------------------- 1 | function c(){return{collapsedGroups:[],isLoading:!1,selectedRecords:[],shouldCheckUniqueSelection:!0,init:function(){this.$wire.$on("deselectAllTableRecords",()=>this.deselectAllRecords()),this.$watch("selectedRecords",()=>{if(!this.shouldCheckUniqueSelection){this.shouldCheckUniqueSelection=!0;return}this.selectedRecords=[...new Set(this.selectedRecords)],this.shouldCheckUniqueSelection=!1})},mountAction:function(e,s=null){this.$wire.set("selectedTableRecords",this.selectedRecords,!1),this.$wire.mountTableAction(e,s)},mountBulkAction:function(e){this.$wire.set("selectedTableRecords",this.selectedRecords,!1),this.$wire.mountTableBulkAction(e)},toggleSelectRecordsOnPage:function(){let e=this.getRecordsOnPage();if(this.areRecordsSelected(e)){this.deselectRecords(e);return}this.selectRecords(e)},toggleSelectRecordsInGroup:async function(e){if(this.isLoading=!0,this.areRecordsSelected(this.getRecordsInGroupOnPage(e))){this.deselectRecords(await this.$wire.getGroupedSelectableTableRecordKeys(e));return}this.selectRecords(await this.$wire.getGroupedSelectableTableRecordKeys(e)),this.isLoading=!1},getRecordsInGroupOnPage:function(e){let s=[];for(let t of this.$root?.getElementsByClassName("fi-ta-record-checkbox")??[])t.dataset.group===e&&s.push(t.value);return s},getRecordsOnPage:function(){let e=[];for(let s of this.$root?.getElementsByClassName("fi-ta-record-checkbox")??[])e.push(s.value);return e},selectRecords:function(e){for(let s of e)this.isRecordSelected(s)||this.selectedRecords.push(s)},deselectRecords:function(e){for(let s of e){let t=this.selectedRecords.indexOf(s);t!==-1&&this.selectedRecords.splice(t,1)}},selectAllRecords:async function(){this.isLoading=!0,this.selectedRecords=await this.$wire.getAllSelectableTableRecordKeys(),this.isLoading=!1},deselectAllRecords:function(){this.selectedRecords=[]},isRecordSelected:function(e){return this.selectedRecords.includes(e)},areRecordsSelected:function(e){return e.every(s=>this.isRecordSelected(s))},toggleCollapseGroup:function(e){if(this.isGroupCollapsed(e)){this.collapsedGroups.splice(this.collapsedGroups.indexOf(e),1);return}this.collapsedGroups.push(e)},isGroupCollapsed:function(e){return this.collapsedGroups.includes(e)},resetCollapsedGroups:function(){this.collapsedGroups=[]}}}export{c as default}; 2 | -------------------------------------------------------------------------------- /app/Models/Users/User.php: -------------------------------------------------------------------------------- 1 | 26 | */ 27 | protected $fillable = [ 28 | 'name', 29 | 'github_id', 30 | 'github_username', 31 | 'description', 32 | 'email', 33 | 'onboarded', 34 | 'password' 35 | ]; 36 | 37 | protected $casts = [ 38 | 'onboarded' => 'boolean', 39 | 'github_id' => 'int' 40 | ]; 41 | 42 | public function getImageUrlAttribute(): string 43 | { 44 | return "https://avatars.githubusercontent.com/u/{$this->github_id}"; 45 | } 46 | 47 | public function tokens(): HasMany 48 | { 49 | return $this->hasMany(Token::class); 50 | } 51 | 52 | public function tasks(): HasMany 53 | { 54 | return $this->hasMany(Progress::class, 'user_id'); 55 | } 56 | 57 | public function details(): HasOne 58 | { 59 | return $this->hasOne(Details::class); 60 | } 61 | 62 | public function modules(): BelongsToMany 63 | { 64 | return $this->belongsToMany( 65 | Module::class, 66 | 'users_modules', 67 | 'user_id', 68 | 'module_id', 69 | )->using(ModuleAttendance::class) 70 | ->withPivot('status') 71 | ->withTimestamps(); 72 | } 73 | 74 | public function onboard(array $payload): void 75 | { 76 | $this->details()->updateOrCreate($payload); 77 | $this->update(['onboarded'=> true]); 78 | } 79 | 80 | public function canAccessPanel(Panel $panel): bool 81 | { 82 | return true; 83 | } 84 | } 85 | -------------------------------------------------------------------------------- /config/broadcasting.php: -------------------------------------------------------------------------------- 1 | env('BROADCAST_DRIVER', 'null'), 19 | 20 | /* 21 | |-------------------------------------------------------------------------- 22 | | Broadcast Connections 23 | |-------------------------------------------------------------------------- 24 | | 25 | | Here you may define all of the broadcast connections that will be used 26 | | to broadcast events to other systems or over websockets. Samples of 27 | | each available type of connection are provided inside this array. 28 | | 29 | */ 30 | 31 | 'connections' => [ 32 | 33 | 'pusher' => [ 34 | 'driver' => 'pusher', 35 | 'key' => env('PUSHER_APP_KEY'), 36 | 'secret' => env('PUSHER_APP_SECRET'), 37 | 'app_id' => env('PUSHER_APP_ID'), 38 | 'options' => [ 39 | 'cluster' => env('PUSHER_APP_CLUSTER'), 40 | 'host' => env('PUSHER_HOST') ?: 'api-'.env('PUSHER_APP_CLUSTER', 'mt1').'.pusher.com', 41 | 'port' => env('PUSHER_PORT', 443), 42 | 'scheme' => env('PUSHER_SCHEME', 'https'), 43 | 'encrypted' => true, 44 | 'useTLS' => env('PUSHER_SCHEME', 'https') === 'https', 45 | ], 46 | 'client_options' => [ 47 | // Guzzle client options: https://docs.guzzlephp.org/en/stable/request-options.html 48 | ], 49 | ], 50 | 51 | 'ably' => [ 52 | 'driver' => 'ably', 53 | 'key' => env('ABLY_KEY'), 54 | ], 55 | 56 | 'redis' => [ 57 | 'driver' => 'redis', 58 | 'connection' => 'default', 59 | ], 60 | 61 | 'log' => [ 62 | 'driver' => 'log', 63 | ], 64 | 65 | 'null' => [ 66 | 'driver' => 'null', 67 | ], 68 | 69 | ], 70 | 71 | ]; 72 | -------------------------------------------------------------------------------- /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "laravel/laravel", 3 | "type": "project", 4 | "description": "The skeleton application for the Laravel framework.", 5 | "keywords": [ 6 | "laravel", 7 | "framework" 8 | ], 9 | "license": "MIT", 10 | "require": { 11 | "php": "^8.1", 12 | "filament/filament": "^3.2", 13 | "filament/notifications": "^3.2", 14 | "guzzlehttp/guzzle": "^7.2", 15 | "laravel/framework": "^10.10", 16 | "laravel/sanctum": "^3.3", 17 | "laravel/socialite": "^5.11", 18 | "laravel/tinker": "^2.8", 19 | "spatie/laravel-markdown": "^2.4" 20 | }, 21 | "require-dev": { 22 | "fakerphp/faker": "^1.9.1", 23 | "laravel/pint": "^1.0", 24 | "laravel/sail": "^1.18", 25 | "lucascudo/laravel-pt-br-localization": "^2.0", 26 | "mockery/mockery": "^1.4.4", 27 | "nunomaduro/collision": "^7.0", 28 | "phpunit/phpunit": "^10.1", 29 | "spatie/laravel-ignition": "^2.0" 30 | }, 31 | "autoload": { 32 | "psr-4": { 33 | "App\\": "app/", 34 | "Database\\Factories\\": "database/factories/", 35 | "Database\\Seeders\\": "database/seeders/" 36 | } 37 | }, 38 | "autoload-dev": { 39 | "psr-4": { 40 | "Tests\\": "tests/" 41 | } 42 | }, 43 | "scripts": { 44 | "post-autoload-dump": [ 45 | "Illuminate\\Foundation\\ComposerScripts::postAutoloadDump", 46 | "@php artisan package:discover --ansi", 47 | "@php artisan filament:upgrade" 48 | ], 49 | "post-update-cmd": [ 50 | "@php artisan vendor:publish --tag=laravel-assets --ansi --force" 51 | ], 52 | "post-root-package-install": [ 53 | "@php -r \"file_exists('.env') || copy('.env.example', '.env');\"" 54 | ], 55 | "post-create-project-cmd": [ 56 | "@php artisan key:generate --ansi" 57 | ] 58 | }, 59 | "extra": { 60 | "laravel": { 61 | "dont-discover": [] 62 | } 63 | }, 64 | "config": { 65 | "optimize-autoloader": true, 66 | "preferred-install": "dist", 67 | "sort-packages": true, 68 | "allow-plugins": { 69 | "pestphp/pest-plugin": true, 70 | "php-http/discovery": true 71 | } 72 | }, 73 | "minimum-stability": "stable", 74 | "prefer-stable": true 75 | } 76 | -------------------------------------------------------------------------------- /app/Providers/Filament/AdminPanelProvider.php: -------------------------------------------------------------------------------- 1 | default() 30 | ->id('admin') 31 | ->path('admin') 32 | ->databaseNotifications() 33 | ->login() 34 | ->colors([ 35 | 'primary' => Color::Blue, 36 | ]) 37 | ->discoverResources(in: app_path('Filament/Resources'), for: 'App\\Filament\\Resources') 38 | ->discoverPages(in: app_path('Filament/Pages'), for: 'App\\Filament\\Pages') 39 | ->pages([ 40 | Pages\Dashboard::class, 41 | ]) 42 | ->discoverWidgets(in: app_path('Filament/Widgets'), for: 'App\\Filament\\Widgets') 43 | ->widgets([ 44 | Widgets\AccountWidget::class, 45 | Widgets\FilamentInfoWidget::class, 46 | ]) 47 | ->middleware([ 48 | EncryptCookies::class, 49 | AddQueuedCookiesToResponse::class, 50 | StartSession::class, 51 | AuthenticateSession::class, 52 | ShareErrorsFromSession::class, 53 | VerifyCsrfToken::class, 54 | SubstituteBindings::class, 55 | DisableBladeIconComponents::class, 56 | DispatchServingFilamentEvent::class, 57 | ]) 58 | ->authMiddleware([ 59 | Authenticate::class, 60 | ])->brandName('Basement Mentorship'); 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /config/filesystems.php: -------------------------------------------------------------------------------- 1 | env('FILESYSTEM_DISK', 'local'), 17 | 18 | /* 19 | |-------------------------------------------------------------------------- 20 | | Filesystem Disks 21 | |-------------------------------------------------------------------------- 22 | | 23 | | Here you may configure as many filesystem "disks" as you wish, and you 24 | | may even configure multiple disks of the same driver. Defaults have 25 | | been set up for each driver as an example of the required values. 26 | | 27 | | Supported Drivers: "local", "ftp", "sftp", "s3" 28 | | 29 | */ 30 | 31 | 'disks' => [ 32 | 33 | 'local' => [ 34 | 'driver' => 'local', 35 | 'root' => storage_path('app'), 36 | 'throw' => false, 37 | ], 38 | 39 | 'public' => [ 40 | 'driver' => 'local', 41 | 'root' => storage_path('app/public'), 42 | 'url' => env('APP_URL').'/storage', 43 | 'visibility' => 'public', 44 | 'throw' => false, 45 | ], 46 | 47 | 's3' => [ 48 | 'driver' => 's3', 49 | 'key' => env('AWS_ACCESS_KEY_ID'), 50 | 'secret' => env('AWS_SECRET_ACCESS_KEY'), 51 | 'region' => env('AWS_DEFAULT_REGION'), 52 | 'bucket' => env('AWS_BUCKET'), 53 | 'url' => env('AWS_URL'), 54 | 'endpoint' => env('AWS_ENDPOINT'), 55 | 'use_path_style_endpoint' => env('AWS_USE_PATH_STYLE_ENDPOINT', false), 56 | 'throw' => false, 57 | ], 58 | 59 | ], 60 | 61 | /* 62 | |-------------------------------------------------------------------------- 63 | | Symbolic Links 64 | |-------------------------------------------------------------------------- 65 | | 66 | | Here you may configure the symbolic links that will be created when the 67 | | `storage:link` Artisan command is executed. The array keys should be 68 | | the locations of the links and the values should be their targets. 69 | | 70 | */ 71 | 72 | 'links' => [ 73 | public_path('storage') => storage_path('app/public'), 74 | ], 75 | 76 | ]; 77 | -------------------------------------------------------------------------------- /public/css/filament/support/support.css: -------------------------------------------------------------------------------- 1 | .fi-pagination-items,.fi-pagination-overview,.fi-pagination-records-per-page-select:not(.fi-compact){display:none}@supports (container-type:inline-size){.fi-pagination{container-type:inline-size}@container (min-width: 28rem){.fi-pagination-records-per-page-select.fi-compact{display:none}.fi-pagination-records-per-page-select:not(.fi-compact){display:inline}}@container (min-width: 56rem){.fi-pagination:not(.fi-simple)>.fi-pagination-previous-btn{display:none}.fi-pagination-overview{display:inline}.fi-pagination:not(.fi-simple)>.fi-pagination-next-btn{display:none}.fi-pagination-items{display:flex}}}@supports not (container-type:inline-size){@media (min-width:640px){.fi-pagination-records-per-page-select.fi-compact{display:none}.fi-pagination-records-per-page-select:not(.fi-compact){display:inline}}@media (min-width:768px){.fi-pagination:not(.fi-simple)>.fi-pagination-previous-btn{display:none}.fi-pagination-overview{display:inline}.fi-pagination:not(.fi-simple)>.fi-pagination-next-btn{display:none}.fi-pagination-items{display:flex}}}.tippy-box[data-animation=fade][data-state=hidden]{opacity:0}[data-tippy-root]{max-width:calc(100vw - 10px)}.tippy-box{background-color:#333;border-radius:4px;color:#fff;font-size:14px;line-height:1.4;outline:0;position:relative;transition-property:transform,visibility,opacity;white-space:normal}.tippy-box[data-placement^=top]>.tippy-arrow{bottom:0}.tippy-box[data-placement^=top]>.tippy-arrow:before{border-top-color:initial;border-width:8px 8px 0;bottom:-7px;left:0;transform-origin:center top}.tippy-box[data-placement^=bottom]>.tippy-arrow{top:0}.tippy-box[data-placement^=bottom]>.tippy-arrow:before{border-bottom-color:initial;border-width:0 8px 8px;left:0;top:-7px;transform-origin:center bottom}.tippy-box[data-placement^=left]>.tippy-arrow{right:0}.tippy-box[data-placement^=left]>.tippy-arrow:before{border-left-color:initial;border-width:8px 0 8px 8px;right:-7px;transform-origin:center left}.tippy-box[data-placement^=right]>.tippy-arrow{left:0}.tippy-box[data-placement^=right]>.tippy-arrow:before{border-right-color:initial;border-width:8px 8px 8px 0;left:-7px;transform-origin:center right}.tippy-box[data-inertia][data-state=visible]{transition-timing-function:cubic-bezier(.54,1.5,.38,1.11)}.tippy-arrow{color:#333;height:16px;width:16px}.tippy-arrow:before{border-color:transparent;border-style:solid;content:"";position:absolute}.tippy-content{padding:5px 9px;position:relative;z-index:1}.tippy-box[data-theme~=light]{background-color:#fff;box-shadow:0 0 20px 4px #9aa1b126,0 4px 80px -8px #24282f40,0 4px 4px -2px #5b5e6926;color:#26323d}.tippy-box[data-theme~=light][data-placement^=top]>.tippy-arrow:before{border-top-color:#fff}.tippy-box[data-theme~=light][data-placement^=bottom]>.tippy-arrow:before{border-bottom-color:#fff}.tippy-box[data-theme~=light][data-placement^=left]>.tippy-arrow:before{border-left-color:#fff}.tippy-box[data-theme~=light][data-placement^=right]>.tippy-arrow:before{border-right-color:#fff}.tippy-box[data-theme~=light]>.tippy-backdrop{background-color:#fff}.tippy-box[data-theme~=light]>.tippy-svg-arrow{fill:#fff}.fi-sortable-ghost{opacity:.3} -------------------------------------------------------------------------------- /resources/views/components/task-list.blade.php: -------------------------------------------------------------------------------- 1 | @props([ 2 | 'tasks', 3 | 'userTasks' 4 | ]) 5 | 6 | 7 |
8 | @foreach($tasks as $task) 9 |
10 |
11 |
12 | 13 | ... 16 | 17 |
18 |
19 |

20 | 21 | : {{ $task->created_at->diffForHumans() }} 22 | 23 | 24 | : {{ $task->progress()->count() }} 25 | 26 | 27 | : {{ $task->progress()->where('status' , 'completed')->count() }} 28 | 29 |

30 |
#{{ $task->id }} - {{ $task->title }}
31 |

32 | {{ $task->description }} 33 |

34 | 35 |
36 |
37 | @if($userTask = $userTasks->first(fn ($userTask) => $userTask->task_id == $task->id)) 38 | @if($userTask->status == 'completed') 39 | Task Completed! 40 | @else 41 | Continuar Tarefa 43 | @endif 44 | @else 45 |
47 | @csrf 48 | 49 |
50 | @endif 51 |
52 |
53 |
54 | 55 |
56 |
57 |
58 |
59 | @endforeach 60 |
61 | -------------------------------------------------------------------------------- /app/Http/Kernel.php: -------------------------------------------------------------------------------- 1 | 15 | */ 16 | protected $middleware = [ 17 | // \App\Http\Middleware\TrustHosts::class, 18 | \App\Http\Middleware\TrustProxies::class, 19 | \Illuminate\Http\Middleware\HandleCors::class, 20 | \App\Http\Middleware\PreventRequestsDuringMaintenance::class, 21 | \Illuminate\Foundation\Http\Middleware\ValidatePostSize::class, 22 | \App\Http\Middleware\TrimStrings::class, 23 | \Illuminate\Foundation\Http\Middleware\ConvertEmptyStringsToNull::class, 24 | ]; 25 | 26 | /** 27 | * The application's route middleware groups. 28 | * 29 | * @var array> 30 | */ 31 | protected $middlewareGroups = [ 32 | 'web' => [ 33 | \App\Http\Middleware\EncryptCookies::class, 34 | \Illuminate\Cookie\Middleware\AddQueuedCookiesToResponse::class, 35 | \Illuminate\Session\Middleware\StartSession::class, 36 | \Illuminate\View\Middleware\ShareErrorsFromSession::class, 37 | \App\Http\Middleware\VerifyCsrfToken::class, 38 | \Illuminate\Routing\Middleware\SubstituteBindings::class, 39 | ], 40 | 41 | 'api' => [ 42 | // \Laravel\Sanctum\Http\Middleware\EnsureFrontendRequestsAreStateful::class, 43 | \Illuminate\Routing\Middleware\ThrottleRequests::class.':api', 44 | \Illuminate\Routing\Middleware\SubstituteBindings::class, 45 | ], 46 | ]; 47 | 48 | /** 49 | * The application's middleware aliases. 50 | * 51 | * Aliases may be used instead of class names to conveniently assign middleware to routes and groups. 52 | * 53 | * @var array 54 | */ 55 | protected $middlewareAliases = [ 56 | 'auth' => \App\Http\Middleware\Authenticate::class, 57 | 'auth.basic' => \Illuminate\Auth\Middleware\AuthenticateWithBasicAuth::class, 58 | 'auth.session' => \Illuminate\Session\Middleware\AuthenticateSession::class, 59 | 'cache.headers' => \Illuminate\Http\Middleware\SetCacheHeaders::class, 60 | 'can' => \Illuminate\Auth\Middleware\Authorize::class, 61 | 'guest' => \App\Http\Middleware\RedirectIfAuthenticated::class, 62 | 'password.confirm' => \Illuminate\Auth\Middleware\RequirePassword::class, 63 | 'precognitive' => \Illuminate\Foundation\Http\Middleware\HandlePrecognitiveRequests::class, 64 | 'signed' => \App\Http\Middleware\ValidateSignature::class, 65 | 'throttle' => \Illuminate\Routing\Middleware\ThrottleRequests::class, 66 | 'verified' => \Illuminate\Auth\Middleware\EnsureEmailIsVerified::class, 67 | 'onboarded' => \App\Http\Middleware\VerifyOnboarding::class, 68 | 'module.attendance' => \App\Http\Middleware\VerifyModuleAttendance::class, 69 | ]; 70 | } 71 | -------------------------------------------------------------------------------- /config/sanctum.php: -------------------------------------------------------------------------------- 1 | explode(',', env('SANCTUM_STATEFUL_DOMAINS', sprintf( 19 | '%s%s', 20 | 'localhost,localhost:3000,127.0.0.1,127.0.0.1:8000,::1', 21 | Sanctum::currentApplicationUrlWithPort() 22 | ))), 23 | 24 | /* 25 | |-------------------------------------------------------------------------- 26 | | Sanctum Guards 27 | |-------------------------------------------------------------------------- 28 | | 29 | | This array contains the authentication guards that will be checked when 30 | | Sanctum is trying to authenticate a request. If none of these guards 31 | | are able to authenticate the request, Sanctum will use the bearer 32 | | token that's present on an incoming request for authentication. 33 | | 34 | */ 35 | 36 | 'guard' => ['web'], 37 | 38 | /* 39 | |-------------------------------------------------------------------------- 40 | | Expiration Minutes 41 | |-------------------------------------------------------------------------- 42 | | 43 | | This value controls the number of minutes until an issued token will be 44 | | considered expired. This will override any values set in the token's 45 | | "expires_at" attribute, but first-party sessions are not affected. 46 | | 47 | */ 48 | 49 | 'expiration' => null, 50 | 51 | /* 52 | |-------------------------------------------------------------------------- 53 | | Token Prefix 54 | |-------------------------------------------------------------------------- 55 | | 56 | | Sanctum can prefix new tokens in order to take advantage of numerous 57 | | security scanning initiatives maintained by open source platforms 58 | | that notify developers if they commit tokens into repositories. 59 | | 60 | | See: https://docs.github.com/en/code-security/secret-scanning/about-secret-scanning 61 | | 62 | */ 63 | 64 | 'token_prefix' => env('SANCTUM_TOKEN_PREFIX', ''), 65 | 66 | /* 67 | |-------------------------------------------------------------------------- 68 | | Sanctum Middleware 69 | |-------------------------------------------------------------------------- 70 | | 71 | | When authenticating your first-party SPA with Sanctum you may need to 72 | | customize some of the middleware Sanctum uses while processing the 73 | | request. You may change the middleware listed below as required. 74 | | 75 | */ 76 | 77 | 'middleware' => [ 78 | 'authenticate_session' => Laravel\Sanctum\Http\Middleware\AuthenticateSession::class, 79 | 'encrypt_cookies' => App\Http\Middleware\EncryptCookies::class, 80 | 'verify_csrf_token' => App\Http\Middleware\VerifyCsrfToken::class, 81 | ], 82 | 83 | ]; 84 | -------------------------------------------------------------------------------- /resources/views/components/navbar.blade.php: -------------------------------------------------------------------------------- 1 | 65 | 66 | -------------------------------------------------------------------------------- /app/Filament/Resources/ModuleAttendanceResource.php: -------------------------------------------------------------------------------- 1 | schema([ 28 | Forms\Components\Select::make('module_id') 29 | ->relationship('module', 'name') 30 | ->disabled() 31 | ->required(), 32 | Forms\Components\Select::make('user_id') 33 | ->relationship('user', 'name') 34 | ->disabled() 35 | ->required(), 36 | Forms\Components\Select::make('status') 37 | ->required() 38 | ->options(ModuleAttendanceEnum::class), 39 | Forms\Components\Textarea::make('description', '') 40 | ]); 41 | } 42 | 43 | public static function table(Table $table): Table 44 | { 45 | // TODO: removing id from table asap. 46 | 47 | 48 | return $table 49 | ->columns([ 50 | Tables\Columns\TextColumn::make('module.name') 51 | ->numeric(), 52 | Tables\Columns\TextColumn::make('user.name') 53 | ->numeric(), 54 | Tables\Columns\TextColumn::make('status') 55 | ->badge(), 56 | Tables\Columns\TextColumn::make('created_at') 57 | ->dateTime() 58 | ->toggleable(isToggledHiddenByDefault: true), 59 | Tables\Columns\TextColumn::make('updated_at') 60 | ->dateTime() 61 | ->toggleable(isToggledHiddenByDefault: true), 62 | ]) 63 | ->filters([ 64 | // 65 | ]) 66 | ->actions([ 67 | AttendanceApprovalAction::make(), 68 | Tables\Actions\EditAction::make(), 69 | ]) 70 | ->bulkActions([ 71 | Tables\Actions\BulkActionGroup::make([ 72 | Tables\Actions\DeleteBulkAction::make(), 73 | ]), 74 | ]); 75 | } 76 | 77 | public static function getRelations(): array 78 | { 79 | return [ 80 | // 81 | ]; 82 | } 83 | 84 | public static function getPages(): array 85 | { 86 | return [ 87 | 'index' => Pages\ListModuleAttendances::route('/'), 88 | 'create' => Pages\CreateModuleAttendance::route('/create'), 89 | 'edit' => Pages\EditModuleAttendance::route('/{record}/edit'), 90 | ]; 91 | } 92 | } 93 | -------------------------------------------------------------------------------- /resources/views/components/module-list.blade.php: -------------------------------------------------------------------------------- 1 | @props([ 2 | 'modules', 3 | 'userModules' 4 | ]) 5 | 6 |

Trilhas de Mentorias

7 |

8 | Se inscreva na que mais te interessar e comece a aprender com a comunidade. 9 |

10 | 11 |
12 | @foreach($modules as $module) 13 |
14 |
15 | ... 16 |
17 |
18 |
19 | 20 | : {{ $module->users()->count() }} 21 | 22 | 23 | : {{ $module->users()->where('status', 'finished')->count() }} 24 | 25 |
26 | 27 | : {{ $module->created_at->format('d/m/Y') }} 28 | 29 |
30 | 31 |
#{{ $module->id }} - {{ $module->name }}
32 |

33 | {{ $module->description }} 34 |

35 | 36 |
37 | @if($userModule = $userModules->find($module)) 38 | 39 | @if($userModule->pivot->status === \App\Enums\Module\ModuleAttendanceEnum::ACCEPTED) 40 | 42 | Continuar Trilha 43 | 44 | @elseif($userModule->pivot->status === \App\Enums\Module\ModuleAttendanceEnum::FINISHED) 45 | 46 | 48 | Trilha finalizada! 49 | 50 | @else 51 | 53 | Aguardando Aprovação 54 | 55 | @endif 56 | @else 57 |
58 | @csrf 59 | 60 |
61 | @endif 62 |
63 | 64 |
65 |
66 |
67 | 68 | @endforeach 69 |
70 | -------------------------------------------------------------------------------- /routes/web.php: -------------------------------------------------------------------------------- 1 | name('landing'); 27 | 28 | if(config('app.env') === 'local') { 29 | Route::get('/mailable', function () { 30 | $mailable = (new MailMessage) 31 | ->subject('Mentorias Fodas') 32 | ->line('Se você está recebendo esse e-mail, significa que você foi aprovado/a em nossa mentoria!') 33 | ->line('A trilha selecionada foi **' . 'trilha-foda' . '**, e você deverá concluí-la na maior calma do mundo!') 34 | ->line("Como isso é um processo de aprimoramento, eu gostaria MUITO que você fizesse as tarefas utilizando os meios comuns de estudo.") 35 | ->line('') 36 | ->action('Começar Trilha', route('module.index')); 37 | 38 | return $mailable; 39 | }); 40 | } 41 | 42 | 43 | 44 | Route::get('/oauth/{provider}/redirect', [AuthController::class, 'getRedirect'])->name('auth.redirect'); 45 | Route::get('/oauth/{provider}/callback', [AuthController::class, 'getAuthenticate'])->name('auth.store'); 46 | Route::post('/oauth/logout', [AuthController::class, 'postLogout']) 47 | ->middleware('auth') 48 | ->name('auth.logout'); 49 | 50 | Route::get('/dashboard', [DashboardController::class, 'getDashboard']) 51 | ->middleware(['auth', 'onboarded']) 52 | ->name('dashboard'); 53 | 54 | Route::prefix('onboarding') 55 | ->middleware('auth') 56 | ->group(function () { 57 | Route::get('/', [OnboardingController::class, 'getOnboarding'])->name('onboarding'); 58 | Route::post('/', [OnboardingController::class, 'postOnboarding'])->name('onboarding.store'); 59 | }); 60 | 61 | 62 | Route::prefix('/modules') 63 | ->middleware(['auth', 'onboarded']) 64 | ->group(function () { 65 | Route::get('/', [ModulesController::class, 'getModules'])->name('module.index'); 66 | Route::post('/{module}/init', [ModulesController::class, 'postInitModule'])->name('module.init'); 67 | Route::prefix('/{module}') 68 | ->middleware('module.attendance') 69 | ->group(function () { 70 | Route::get('/', [ModulesController::class, 'getModule'])->name('modules.show'); 71 | Route::post('/tasks/{task}/init', [TasksController::class, 'postInitTask'])->name('tasks.init'); 72 | Route::post('/tasks/{progress}/{action}', [TasksController::class, 'postTaskAction'])->name('tasks.action'); 73 | Route::get('/tasks/{taskProgress}', [TasksController::class, 'getTask'])->name('tasks.show'); 74 | }); 75 | }); 76 | 77 | 78 | -------------------------------------------------------------------------------- /config/cache.php: -------------------------------------------------------------------------------- 1 | env('CACHE_DRIVER', 'file'), 19 | 20 | /* 21 | |-------------------------------------------------------------------------- 22 | | Cache Stores 23 | |-------------------------------------------------------------------------- 24 | | 25 | | Here you may define all of the cache "stores" for your application as 26 | | well as their drivers. You may even define multiple stores for the 27 | | same cache driver to group types of items stored in your caches. 28 | | 29 | | Supported drivers: "apc", "array", "database", "file", 30 | | "memcached", "redis", "dynamodb", "octane", "null" 31 | | 32 | */ 33 | 34 | 'stores' => [ 35 | 36 | 'apc' => [ 37 | 'driver' => 'apc', 38 | ], 39 | 40 | 'array' => [ 41 | 'driver' => 'array', 42 | 'serialize' => false, 43 | ], 44 | 45 | 'database' => [ 46 | 'driver' => 'database', 47 | 'table' => 'cache', 48 | 'connection' => null, 49 | 'lock_connection' => null, 50 | ], 51 | 52 | 'file' => [ 53 | 'driver' => 'file', 54 | 'path' => storage_path('framework/cache/data'), 55 | 'lock_path' => storage_path('framework/cache/data'), 56 | ], 57 | 58 | 'memcached' => [ 59 | 'driver' => 'memcached', 60 | 'persistent_id' => env('MEMCACHED_PERSISTENT_ID'), 61 | 'sasl' => [ 62 | env('MEMCACHED_USERNAME'), 63 | env('MEMCACHED_PASSWORD'), 64 | ], 65 | 'options' => [ 66 | // Memcached::OPT_CONNECT_TIMEOUT => 2000, 67 | ], 68 | 'servers' => [ 69 | [ 70 | 'host' => env('MEMCACHED_HOST', '127.0.0.1'), 71 | 'port' => env('MEMCACHED_PORT', 11211), 72 | 'weight' => 100, 73 | ], 74 | ], 75 | ], 76 | 77 | 'redis' => [ 78 | 'driver' => 'redis', 79 | 'connection' => 'cache', 80 | 'lock_connection' => 'default', 81 | ], 82 | 83 | 'dynamodb' => [ 84 | 'driver' => 'dynamodb', 85 | 'key' => env('AWS_ACCESS_KEY_ID'), 86 | 'secret' => env('AWS_SECRET_ACCESS_KEY'), 87 | 'region' => env('AWS_DEFAULT_REGION', 'us-east-1'), 88 | 'table' => env('DYNAMODB_CACHE_TABLE', 'cache'), 89 | 'endpoint' => env('DYNAMODB_ENDPOINT'), 90 | ], 91 | 92 | 'octane' => [ 93 | 'driver' => 'octane', 94 | ], 95 | 96 | ], 97 | 98 | /* 99 | |-------------------------------------------------------------------------- 100 | | Cache Key Prefix 101 | |-------------------------------------------------------------------------- 102 | | 103 | | When utilizing the APC, database, memcached, Redis, or DynamoDB cache 104 | | stores there might be other applications using the same cache. For 105 | | that reason, you may prefix every cache key to avoid collisions. 106 | | 107 | */ 108 | 109 | 'prefix' => env('CACHE_PREFIX', Str::slug(env('APP_NAME', 'laravel'), '_').'_cache_'), 110 | 111 | ]; 112 | -------------------------------------------------------------------------------- /config/queue.php: -------------------------------------------------------------------------------- 1 | env('QUEUE_CONNECTION', 'sync'), 17 | 18 | /* 19 | |-------------------------------------------------------------------------- 20 | | Queue Connections 21 | |-------------------------------------------------------------------------- 22 | | 23 | | Here you may configure the connection information for each server that 24 | | is used by your application. A default configuration has been added 25 | | for each back-end shipped with Laravel. You are free to add more. 26 | | 27 | | Drivers: "sync", "database", "beanstalkd", "sqs", "redis", "null" 28 | | 29 | */ 30 | 31 | 'connections' => [ 32 | 33 | 'sync' => [ 34 | 'driver' => 'sync', 35 | ], 36 | 37 | 'database' => [ 38 | 'driver' => 'database', 39 | 'table' => 'jobs', 40 | 'queue' => 'default', 41 | 'retry_after' => 90, 42 | 'after_commit' => false, 43 | ], 44 | 45 | 'beanstalkd' => [ 46 | 'driver' => 'beanstalkd', 47 | 'host' => 'localhost', 48 | 'queue' => 'default', 49 | 'retry_after' => 90, 50 | 'block_for' => 0, 51 | 'after_commit' => false, 52 | ], 53 | 54 | 'sqs' => [ 55 | 'driver' => 'sqs', 56 | 'key' => env('AWS_ACCESS_KEY_ID'), 57 | 'secret' => env('AWS_SECRET_ACCESS_KEY'), 58 | 'prefix' => env('SQS_PREFIX', 'https://sqs.us-east-1.amazonaws.com/your-account-id'), 59 | 'queue' => env('SQS_QUEUE', 'default'), 60 | 'suffix' => env('SQS_SUFFIX'), 61 | 'region' => env('AWS_DEFAULT_REGION', 'us-east-1'), 62 | 'after_commit' => false, 63 | ], 64 | 65 | 'redis' => [ 66 | 'driver' => 'redis', 67 | 'connection' => 'default', 68 | 'queue' => env('REDIS_QUEUE', 'default'), 69 | 'retry_after' => 90, 70 | 'block_for' => null, 71 | 'after_commit' => false, 72 | ], 73 | 74 | ], 75 | 76 | /* 77 | |-------------------------------------------------------------------------- 78 | | Job Batching 79 | |-------------------------------------------------------------------------- 80 | | 81 | | The following options configure the database and table that store job 82 | | batching information. These options can be updated to any database 83 | | connection and table which has been defined by your application. 84 | | 85 | */ 86 | 87 | 'batching' => [ 88 | 'database' => env('DB_CONNECTION', 'mysql'), 89 | 'table' => 'job_batches', 90 | ], 91 | 92 | /* 93 | |-------------------------------------------------------------------------- 94 | | Failed Queue Jobs 95 | |-------------------------------------------------------------------------- 96 | | 97 | | These options configure the behavior of failed queue job logging so you 98 | | can control which database and table are used to store the jobs that 99 | | have failed. You may change them to any database / table you wish. 100 | | 101 | */ 102 | 103 | 'failed' => [ 104 | 'driver' => env('QUEUE_FAILED_DRIVER', 'database-uuids'), 105 | 'database' => env('DB_CONNECTION', 'mysql'), 106 | 'table' => 'failed_jobs', 107 | ], 108 | 109 | ]; 110 | -------------------------------------------------------------------------------- /database/seeders/CourseSeeder.php: -------------------------------------------------------------------------------- 1 | 'Desenvolvimento para Iniciantes', 18 | 'description' => 'A trilha é feita para pessoas que não tem o conhecimento dos fundamentos da internet.', 19 | 'thumbnail_url' => 'https://i.imgur.com/Zb2du2E.jpeg', 20 | 'tasks' => [ 21 | [ 22 | 'content' => [ 23 | 'title' => 'Redação ENEM Tech', 24 | 'thumbnail_url' => 'https://i.imgur.com/uILTzpv.jpeg', 25 | 'description' => 'Escreva um texto dissertativo-argumentativo sobre a importância da tecnologia na sociedade.', 26 | 'deadline' => now()->addDays(7), 27 | 'order' => 1, 28 | 'tips' => [ 29 | 'https://www.w3schools.com/html/', 30 | 'https://developer.mozilla.org/pt-BR/docs/Web/HTML' 31 | ], 32 | ], 33 | 'todos' => [ 34 | ['description' => 'Faça um rascunho das suas ideias'], 35 | ['description' => 'Valide as fontes de informação e cite-as no texto'], 36 | ['description' => 'Escreva algo com inicio, meio e fim.'] 37 | ], 38 | ], 39 | [ 40 | 'content' => [ 41 | 'title' => 'Browsers são FODAS!', 42 | 'thumbnail_url' => 'https://i.imgur.com/eAhXBii.jpeg', 43 | 'description' => 'Entenda como os navegadores funcionam e como eles interpretam o HTML, CSS e JavaScript.', 44 | 'deadline' => now()->addDays(14), 45 | 'order' => 2, 46 | 'tips' => [ 47 | 'https://www.w3schools.com/css/', 48 | 'https://developer.mozilla.org/pt-BR/docs/Web/CSS' 49 | ] 50 | ], 51 | 'todos' => [ 52 | ['description' => 'Faça um rascunho das suas ideias'], 53 | ], 54 | ], 55 | [ 56 | 'content' => [ 57 | 'title' => 'Banco de Dados: uma abordagem pragmática', 58 | 'thumbnail_url' => 'https://i.imgur.com/chABRAr.jpeg', 59 | 'description' => 'Antes de escolher um banco de dados, entenda as diferenças entre os tipos de bancos de dados.', 60 | 'deadline' => now()->addDays(21), 61 | 'order' => 3, 62 | 63 | 'tips' => [ 64 | 'https://www.w3schools.com/js/', 65 | 'https://developer.mozilla.org/pt-BR/docs/Web/JavaScript' 66 | ], 67 | ], 68 | 'todos' => [ 69 | ['description' => 'Faça um rascunho das suas ideias'], 70 | ], 71 | ] 72 | ] 73 | ] 74 | ]; 75 | 76 | 77 | foreach ($mentorings as $mentoring) { 78 | /** @var Module $module */ 79 | $tasks = $mentoring['tasks']; 80 | unset($mentoring['tasks']); 81 | 82 | $module = Module::query()->create($mentoring); 83 | foreach ($tasks as $task) { 84 | 85 | $taskModel = $module->tasks()->create($task['content']); 86 | 87 | foreach($task['todos'] as $todo) { 88 | $taskModel->todos()->create($todo); 89 | } 90 | } 91 | } 92 | } 93 | } 94 | -------------------------------------------------------------------------------- /config/auth.php: -------------------------------------------------------------------------------- 1 | [ 17 | 'guard' => 'web', 18 | 'passwords' => 'users', 19 | ], 20 | 21 | /* 22 | |-------------------------------------------------------------------------- 23 | | Authentication Guards 24 | |-------------------------------------------------------------------------- 25 | | 26 | | Next, you may define every authentication guard for your application. 27 | | Of course, a great default configuration has been defined for you 28 | | here which uses session storage and the Eloquent user provider. 29 | | 30 | | All authentication drivers have a user provider. This defines how the 31 | | users are actually retrieved out of your database or other storage 32 | | mechanisms used by this application to persist your user's data. 33 | | 34 | | Supported: "session" 35 | | 36 | */ 37 | 38 | 'guards' => [ 39 | 'web' => [ 40 | 'driver' => 'session', 41 | 'provider' => 'users', 42 | ], 43 | ], 44 | 45 | /* 46 | |-------------------------------------------------------------------------- 47 | | User Providers 48 | |-------------------------------------------------------------------------- 49 | | 50 | | All authentication drivers have a user provider. This defines how the 51 | | users are actually retrieved out of your database or other storage 52 | | mechanisms used by this application to persist your user's data. 53 | | 54 | | If you have multiple user tables or models you may configure multiple 55 | | sources which represent each model / table. These sources may then 56 | | be assigned to any extra authentication guards you have defined. 57 | | 58 | | Supported: "database", "eloquent" 59 | | 60 | */ 61 | 62 | 'providers' => [ 63 | 'users' => [ 64 | 'driver' => 'eloquent', 65 | 'model' => \App\Models\Users\User::class, 66 | ], 67 | 68 | // 'users' => [ 69 | // 'driver' => 'database', 70 | // 'table' => 'users', 71 | // ], 72 | ], 73 | 74 | /* 75 | |-------------------------------------------------------------------------- 76 | | Resetting Passwords 77 | |-------------------------------------------------------------------------- 78 | | 79 | | You may specify multiple password reset configurations if you have more 80 | | than one user table or model in the application and you want to have 81 | | separate password reset settings based on the specific user types. 82 | | 83 | | The expiry time is the number of minutes that each reset token will be 84 | | considered valid. This security feature keeps tokens short-lived so 85 | | they have less time to be guessed. You may change this as needed. 86 | | 87 | | The throttle setting is the number of seconds a user must wait before 88 | | generating more password reset tokens. This prevents the user from 89 | | quickly generating a very large amount of password reset tokens. 90 | | 91 | */ 92 | 93 | 'passwords' => [ 94 | 'users' => [ 95 | 'provider' => 'users', 96 | 'table' => 'password_reset_tokens', 97 | 'expire' => 60, 98 | 'throttle' => 60, 99 | ], 100 | ], 101 | 102 | /* 103 | |-------------------------------------------------------------------------- 104 | | Password Confirmation Timeout 105 | |-------------------------------------------------------------------------- 106 | | 107 | | Here you may define the amount of seconds before a password confirmation 108 | | times out and the user is prompted to re-enter their password via the 109 | | confirmation screen. By default, the timeout lasts for three hours. 110 | | 111 | */ 112 | 113 | 'password_timeout' => 10800, 114 | 115 | ]; 116 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Basement Mentorships 2 | 3 | Basement Mentorships is a web application that allows users to find mentors and mentees in the tech industry. Users can create a profile, search for mentors and mentees, and request mentorships. 4 | 5 | 6 | ## About the project 7 | 8 | Since I decided to mentor many people as possible and asyncronously, came the need of a simple platform to handle that. 9 | FYI this code doesn't follow the best practices and I would not tell you to use this code to study until this message vanishes lol. 10 | 11 | At the user side, we're running: 12 | 13 | - Laravel Blade 14 | - Bootstrap 15 | - jQuery 16 | 17 | and on the Admin side, we're running `FilamentPHP`. 18 | 19 | This will be the stack until I decided to do something better (on user side). 20 | 21 | ### Authenticatin 22 | 23 | The application uses GitHub OAuth for authentication since it's a tech platform and most of the users will have a GitHub account. 24 | 25 | ### Models 26 | 27 | Just a quick explanation of the Models used in the project so far. 28 | 29 | | Model | Description | 30 | |----------|---------------------------------------------------------| 31 | | User | Meentored person. | 32 | | Token | OAuth Credentials for requesting further details. | 33 | | Details | Onboarding information for mentoring approval purposes. | 34 | | Progress | User task progress with status enumeration. | 35 | | Module | Mentoring module that the user will apply. | 36 | | Task | Task of a specific module | 37 | | Todo | Items of a task that would be cool to deliver. | 38 | 39 | ### Handling Module Acceptance 40 | 41 | The most important table for this project is the `users_modules` which handles the acceptance of a mentee in a specific mentoring. 42 | 43 | The pivot is managed by the `ModuleAttendanceEnum` with the flags: 44 | ```php 45 | namespace App\Enums\Module; 46 | 47 | enum ModuleAttendanceEnum: string 48 | { 49 | case ON_HOLD = 'onhold'; 50 | case ACCEPTED = 'accepted'; 51 | case FINISHED = 'finished'; 52 | } 53 | ``` 54 | 55 | and also the Todo Tasks 56 | 57 | ## Prerequisites 58 | 59 | - PHP 8.2 or higher 60 | - Composer 61 | - Node.js and npm 62 | 63 | ## Installation 64 | 65 | 1. Clone the repository: 66 | ``` 67 | git clone https://github.com/DanielHe4rt/your-repo.git 68 | ``` 69 | 70 | 2. Navigate to the project directory: 71 | ``` 72 | cd your-repo 73 | ``` 74 | 75 | 3. Install PHP dependencies: 76 | ``` 77 | composer install 78 | ``` 79 | 80 | 4. Install JavaScript dependencies: 81 | ``` 82 | npm install 83 | ``` 84 | 85 | 5. Copy the example environment file and make the required configuration changes in the `.env` file: 86 | ``` 87 | cp .env.example .env 88 | ``` 89 | 90 | 6. Generate a new application key: 91 | ``` 92 | php artisan key:generate 93 | ``` 94 | 95 | 7. Run the database migrations: 96 | ``` 97 | php artisan migrate 98 | ``` 99 | 100 | ## Registration with GitHub OAuth 101 | 102 | ### 1. Create a new GitHub Application 103 | 104 | Create a new [GitHub Application](https://github.com/settings/apps) on GitHub with the callback URL below: 105 | 106 | ``` 107 | http://localhost:8000/oauth/github/callback 108 | ``` 109 | 110 | > ![TIP] 111 | > Don't forget to change to your domain instead `localhost` when deploying to production. 112 | 113 | ### 2. Updating the `.env` file 114 | 115 | Add the `GITHUB_CLIENT_ID` and `GITHUB_CLIENT_SECRET` to the `.env` file: 116 | 117 | ``` 118 | GITHUB_CLIENT_ID="your-client-id" 119 | GITHUB_CLIENT_SECRET="your-client-secret" 120 | GITHUB_CLIENT_REDIRECT="http://localhost:8000/oauth/github/callback" 121 | ``` 122 | 123 | ## Usage 124 | 125 | To start the development server, run the following commands: 126 | 127 | ```bash 128 | # terminal 1 129 | php artisan serve 130 | ``` 131 | 132 | ```bash 133 | # terminal 2 134 | npm run dev 135 | ``` 136 | 137 | and this will bring the application up at [http://localhost:8000](http://localhost:8000). 138 | 139 | ## License 140 | 141 | This project is open-sourced software licensed under the [MIT license](https://opensource.org/licenses/MIT). 142 | 143 | ## Contributing 144 | 145 | Contributions are what make the open source community such an amazing place to learn, inspire, and create. Any contributions you make are greatly appreciated. 146 | -------------------------------------------------------------------------------- /config/mail.php: -------------------------------------------------------------------------------- 1 | env('MAIL_MAILER', 'smtp'), 17 | 18 | /* 19 | |-------------------------------------------------------------------------- 20 | | Mailer Configurations 21 | |-------------------------------------------------------------------------- 22 | | 23 | | Here you may configure all of the mailers used by your application plus 24 | | their respective settings. Several examples have been configured for 25 | | you and you are free to add your own as your application requires. 26 | | 27 | | Laravel supports a variety of mail "transport" drivers to be used while 28 | | sending an e-mail. You will specify which one you are using for your 29 | | mailers below. You are free to add additional mailers as required. 30 | | 31 | | Supported: "smtp", "sendmail", "mailgun", "ses", "ses-v2", 32 | | "postmark", "log", "array", "failover", "roundrobin" 33 | | 34 | */ 35 | 36 | 'mailers' => [ 37 | 'smtp' => [ 38 | 'transport' => 'smtp', 39 | 'url' => env('MAIL_URL'), 40 | 'host' => env('MAIL_HOST', 'smtp.mailgun.org'), 41 | 'port' => env('MAIL_PORT', 587), 42 | 'encryption' => env('MAIL_ENCRYPTION', 'tls'), 43 | 'username' => env('MAIL_USERNAME'), 44 | 'password' => env('MAIL_PASSWORD'), 45 | 'timeout' => null, 46 | 'local_domain' => env('MAIL_EHLO_DOMAIN'), 47 | ], 48 | 49 | 'ses' => [ 50 | 'transport' => 'ses', 51 | ], 52 | 53 | 'postmark' => [ 54 | 'transport' => 'postmark', 55 | // 'message_stream_id' => null, 56 | // 'client' => [ 57 | // 'timeout' => 5, 58 | // ], 59 | ], 60 | 61 | 'mailgun' => [ 62 | 'transport' => 'mailgun', 63 | // 'client' => [ 64 | // 'timeout' => 5, 65 | // ], 66 | ], 67 | 68 | 'sendmail' => [ 69 | 'transport' => 'sendmail', 70 | 'path' => env('MAIL_SENDMAIL_PATH', '/usr/sbin/sendmail -bs -i'), 71 | ], 72 | 73 | 'log' => [ 74 | 'transport' => 'log', 75 | 'channel' => env('MAIL_LOG_CHANNEL'), 76 | ], 77 | 78 | 'array' => [ 79 | 'transport' => 'array', 80 | ], 81 | 82 | 'failover' => [ 83 | 'transport' => 'failover', 84 | 'mailers' => [ 85 | 'smtp', 86 | 'log', 87 | ], 88 | ], 89 | 90 | 'roundrobin' => [ 91 | 'transport' => 'roundrobin', 92 | 'mailers' => [ 93 | 'ses', 94 | 'postmark', 95 | ], 96 | ], 97 | ], 98 | 99 | /* 100 | |-------------------------------------------------------------------------- 101 | | Global "From" Address 102 | |-------------------------------------------------------------------------- 103 | | 104 | | You may wish for all e-mails sent by your application to be sent from 105 | | the same address. Here, you may specify a name and address that is 106 | | used globally for all e-mails that are sent by your application. 107 | | 108 | */ 109 | 110 | 'from' => [ 111 | 'address' => env('MAIL_FROM_ADDRESS', 'hello@example.com'), 112 | 'name' => env('MAIL_FROM_NAME', 'Example'), 113 | ], 114 | 115 | /* 116 | |-------------------------------------------------------------------------- 117 | | Markdown Mail Settings 118 | |-------------------------------------------------------------------------- 119 | | 120 | | If you are using Markdown based email rendering, you may configure your 121 | | theme and component paths here, allowing you to customize the design 122 | | of the emails. Or, you may simply stick with the Laravel defaults! 123 | | 124 | */ 125 | 126 | 'markdown' => [ 127 | 'theme' => 'default', 128 | 129 | 'paths' => [ 130 | resource_path('views/vendor/mail'), 131 | ], 132 | ], 133 | 134 | ]; 135 | -------------------------------------------------------------------------------- /config/logging.php: -------------------------------------------------------------------------------- 1 | env('LOG_CHANNEL', 'stack'), 22 | 23 | /* 24 | |-------------------------------------------------------------------------- 25 | | Deprecations Log Channel 26 | |-------------------------------------------------------------------------- 27 | | 28 | | This option controls the log channel that should be used to log warnings 29 | | regarding deprecated PHP and library features. This allows you to get 30 | | your application ready for upcoming major versions of dependencies. 31 | | 32 | */ 33 | 34 | 'deprecations' => [ 35 | 'channel' => env('LOG_DEPRECATIONS_CHANNEL', 'null'), 36 | 'trace' => false, 37 | ], 38 | 39 | /* 40 | |-------------------------------------------------------------------------- 41 | | Log Channels 42 | |-------------------------------------------------------------------------- 43 | | 44 | | Here you may configure the log channels for your application. Out of 45 | | the box, Laravel uses the Monolog PHP logging library. This gives 46 | | you a variety of powerful log handlers / formatters to utilize. 47 | | 48 | | Available Drivers: "single", "daily", "slack", "syslog", 49 | | "errorlog", "monolog", 50 | | "custom", "stack" 51 | | 52 | */ 53 | 54 | 'channels' => [ 55 | 'stack' => [ 56 | 'driver' => 'stack', 57 | 'channels' => ['single'], 58 | 'ignore_exceptions' => false, 59 | ], 60 | 61 | 'single' => [ 62 | 'driver' => 'single', 63 | 'path' => storage_path('logs/laravel.log'), 64 | 'level' => env('LOG_LEVEL', 'debug'), 65 | 'replace_placeholders' => true, 66 | ], 67 | 68 | 'daily' => [ 69 | 'driver' => 'daily', 70 | 'path' => storage_path('logs/laravel.log'), 71 | 'level' => env('LOG_LEVEL', 'debug'), 72 | 'days' => 14, 73 | 'replace_placeholders' => true, 74 | ], 75 | 76 | 'slack' => [ 77 | 'driver' => 'slack', 78 | 'url' => env('LOG_SLACK_WEBHOOK_URL'), 79 | 'username' => 'Laravel Log', 80 | 'emoji' => ':boom:', 81 | 'level' => env('LOG_LEVEL', 'critical'), 82 | 'replace_placeholders' => true, 83 | ], 84 | 85 | 'papertrail' => [ 86 | 'driver' => 'monolog', 87 | 'level' => env('LOG_LEVEL', 'debug'), 88 | 'handler' => env('LOG_PAPERTRAIL_HANDLER', SyslogUdpHandler::class), 89 | 'handler_with' => [ 90 | 'host' => env('PAPERTRAIL_URL'), 91 | 'port' => env('PAPERTRAIL_PORT'), 92 | 'connectionString' => 'tls://'.env('PAPERTRAIL_URL').':'.env('PAPERTRAIL_PORT'), 93 | ], 94 | 'processors' => [PsrLogMessageProcessor::class], 95 | ], 96 | 97 | 'stderr' => [ 98 | 'driver' => 'monolog', 99 | 'level' => env('LOG_LEVEL', 'debug'), 100 | 'handler' => StreamHandler::class, 101 | 'formatter' => env('LOG_STDERR_FORMATTER'), 102 | 'with' => [ 103 | 'stream' => 'php://stderr', 104 | ], 105 | 'processors' => [PsrLogMessageProcessor::class], 106 | ], 107 | 108 | 'syslog' => [ 109 | 'driver' => 'syslog', 110 | 'level' => env('LOG_LEVEL', 'debug'), 111 | 'facility' => LOG_USER, 112 | 'replace_placeholders' => true, 113 | ], 114 | 115 | 'errorlog' => [ 116 | 'driver' => 'errorlog', 117 | 'level' => env('LOG_LEVEL', 'debug'), 118 | 'replace_placeholders' => true, 119 | ], 120 | 121 | 'null' => [ 122 | 'driver' => 'monolog', 123 | 'handler' => NullHandler::class, 124 | ], 125 | 126 | 'emergency' => [ 127 | 'path' => storage_path('logs/laravel.log'), 128 | ], 129 | ], 130 | 131 | ]; 132 | -------------------------------------------------------------------------------- /public/js/filament/support/async-alpine.js: -------------------------------------------------------------------------------- 1 | (()=>{(()=>{var d=Object.defineProperty,m=t=>d(t,"__esModule",{value:!0}),f=(t,e)=>{m(t);for(var i in e)d(t,i,{get:e[i],enumerable:!0})},o={};f(o,{eager:()=>g,event:()=>w,idle:()=>y,media:()=>b,visible:()=>E});var c=()=>!0,g=c,v=({component:t,argument:e})=>new Promise(i=>{if(e)window.addEventListener(e,()=>i(),{once:!0});else{let n=a=>{a.detail.id===t.id&&(window.removeEventListener("async-alpine:load",n),i())};window.addEventListener("async-alpine:load",n)}}),w=v,x=()=>new Promise(t=>{"requestIdleCallback"in window?window.requestIdleCallback(t):setTimeout(t,200)}),y=x,A=({argument:t})=>new Promise(e=>{if(!t)return console.log("Async Alpine: media strategy requires a media query. Treating as 'eager'"),e();let i=window.matchMedia(`(${t})`);i.matches?e():i.addEventListener("change",e,{once:!0})}),b=A,$=({component:t,argument:e})=>new Promise(i=>{let n=e||"0px 0px 0px 0px",a=new IntersectionObserver(r=>{r[0].isIntersecting&&(a.disconnect(),i())},{rootMargin:n});a.observe(t.el)}),E=$;function P(t){let e=q(t),i=u(e);return i.type==="method"?{type:"expression",operator:"&&",parameters:[i]}:i}function q(t){let e=/\s*([()])\s*|\s*(\|\||&&|\|)\s*|\s*((?:[^()&|]+\([^()]+\))|[^()&|]+)\s*/g,i=[],n;for(;(n=e.exec(t))!==null;){let[,a,r,s]=n;if(a!==void 0)i.push({type:"parenthesis",value:a});else if(r!==void 0)i.push({type:"operator",value:r==="|"?"&&":r});else{let p={type:"method",method:s.trim()};s.includes("(")&&(p.method=s.substring(0,s.indexOf("(")).trim(),p.argument=s.substring(s.indexOf("(")+1,s.indexOf(")"))),s.method==="immediate"&&(s.method="eager"),i.push(p)}}return i}function u(t){let e=h(t);for(;t.length>0&&(t[0].value==="&&"||t[0].value==="|"||t[0].value==="||");){let i=t.shift().value,n=h(t);e.type==="expression"&&e.operator===i?e.parameters.push(n):e={type:"expression",operator:i,parameters:[e,n]}}return e}function h(t){if(t[0].value==="("){t.shift();let e=u(t);return t[0].value===")"&&t.shift(),e}else return t.shift()}var _="__internal_",l={Alpine:null,_options:{prefix:"ax-",alpinePrefix:"x-",root:"load",inline:"load-src",defaultStrategy:"eager"},_alias:!1,_data:{},_realIndex:0,get _index(){return this._realIndex++},init(t,e={}){return this.Alpine=t,this._options={...this._options,...e},this},start(){return this._processInline(),this._setupComponents(),this._mutations(),this},data(t,e=!1){return this._data[t]={loaded:!1,download:e},this},url(t,e){!t||!e||(this._data[t]||this.data(t),this._data[t].download=()=>import(this._parseUrl(e)))},alias(t){this._alias=t},_processInline(){let t=document.querySelectorAll(`[${this._options.prefix}${this._options.inline}]`);for(let e of t)this._inlineElement(e)},_inlineElement(t){let e=t.getAttribute(`${this._options.alpinePrefix}data`),i=t.getAttribute(`${this._options.prefix}${this._options.inline}`);if(!e||!i)return;let n=this._parseName(e);this.url(n,i)},_setupComponents(){let t=document.querySelectorAll(`[${this._options.prefix}${this._options.root}]`);for(let e of t)this._setupComponent(e)},_setupComponent(t){let e=t.getAttribute(`${this._options.alpinePrefix}data`);t.setAttribute(`${this._options.alpinePrefix}ignore`,"");let i=this._parseName(e),n=t.getAttribute(`${this._options.prefix}${this._options.root}`)||this._options.defaultStrategy;this._componentStrategy({name:i,strategy:n,el:t,id:t.id||this._index})},async _componentStrategy(t){let e=P(t.strategy);await this._generateRequirements(t,e),await this._download(t.name),this._activate(t)},_generateRequirements(t,e){if(e.type==="expression"){if(e.operator==="&&")return Promise.all(e.parameters.map(i=>this._generateRequirements(t,i)));if(e.operator==="||")return Promise.any(e.parameters.map(i=>this._generateRequirements(t,i)))}return o[e.method]?o[e.method]({component:t,argument:e.argument}):!1},async _download(t){if(t.startsWith(_)||(this._handleAlias(t),!this._data[t]||this._data[t].loaded))return;let e=await this._getModule(t);this.Alpine.data(t,e),this._data[t].loaded=!0},async _getModule(t){if(!this._data[t])return;let e=await this._data[t].download(t);return typeof e=="function"?e:e[t]||e.default||Object.values(e)[0]||!1},_activate(t){this.Alpine.destroyTree(t.el),t.el.removeAttribute(`${this._options.alpinePrefix}ignore`),t.el._x_ignore=!1,this.Alpine.initTree(t.el)},_mutations(){new MutationObserver(t=>{for(let e of t)if(e.addedNodes)for(let i of e.addedNodes)i.nodeType===1&&(i.hasAttribute(`${this._options.prefix}${this._options.root}`)&&this._mutationEl(i),i.querySelectorAll(`[${this._options.prefix}${this._options.root}]`).forEach(n=>this._mutationEl(n)))}).observe(document,{attributes:!0,childList:!0,subtree:!0})},_mutationEl(t){t.hasAttribute(`${this._options.prefix}${this._options.inline}`)&&this._inlineElement(t),this._setupComponent(t)},_handleAlias(t){if(!(!this._alias||this._data[t])){if(typeof this._alias=="function"){this.data(t,this._alias);return}this.url(t,this._alias.replaceAll("[name]",t))}},_parseName(t){return(t||"").split(/[({]/g)[0]||`${_}${this._index}`},_parseUrl(t){return new RegExp("^(?:[a-z+]+:)?//","i").test(t)?t:new URL(t,document.baseURI).href}};document.addEventListener("alpine:init",()=>{window.AsyncAlpine=l,l.init(Alpine,window.AsyncAlpineOptions||{}),document.dispatchEvent(new CustomEvent("async-alpine:init")),l.start()})})();})(); 2 | -------------------------------------------------------------------------------- /public/assets/head.svg: -------------------------------------------------------------------------------- 1 | 2 | banners 3 | 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /app/Filament/Resources/ModuleResource.php: -------------------------------------------------------------------------------- 1 | schema([ 27 | TextInput::make('name') 28 | ->required() 29 | ->maxLength(255), 30 | TextInput::make('thumbnail_url') 31 | ->label('Thumbnail') 32 | ->maxLength(255), 33 | Textarea::make('description') 34 | ->required() 35 | ->maxLength(65535) 36 | ->columnSpanFull(), 37 | Repeater::make('tasks')->relationship() 38 | ->schema([ 39 | Tabs::make('') 40 | ->tabs([ 41 | Tabs\Tab::make('Task Informations') 42 | ->schema([ 43 | TextInput::make('title') 44 | ->required() 45 | ->maxLength(255), 46 | TextInput::make('thumbnail_url') 47 | ->label('Thumbnail') 48 | ->maxLength(255), 49 | Textarea::make('description') 50 | ->columnSpanFull() 51 | ->required() 52 | ->maxLength(255), 53 | TextInput::make('deadline'), 54 | TextInput::make('order') 55 | ->type('number') 56 | ->required(), 57 | TagsInput::make('tips') 58 | ->columnSpanFull() 59 | ->label('Tips') 60 | ->placeholder('Enter a tip'), 61 | ]), 62 | Tabs\Tab::make('Task ToDos') 63 | ->schema([ 64 | Repeater::make('todos') 65 | ->relationship() 66 | ->schema([ 67 | TextInput::make('description') 68 | ->required() 69 | ->maxLength(255), 70 | ]), 71 | ]), 72 | 73 | ])->columnSpanFull(), 74 | ])->columns(2)->columnSpanFull()->grid(2)->collapsible(), 75 | ]); 76 | } 77 | 78 | public static function table(Table $table): Table 79 | { 80 | return $table 81 | ->columns([ 82 | Tables\Columns\TextColumn::make('name') 83 | ->searchable(), 84 | Tables\Columns\TextColumn::make('thumbnail_url') 85 | ->label('Thumbnail') 86 | ->searchable(), 87 | Tables\Columns\TextColumn::make('created_at') 88 | ->dateTime() 89 | ->sortable() 90 | ->toggleable(isToggledHiddenByDefault: true), 91 | Tables\Columns\TextColumn::make('updated_at') 92 | ->dateTime() 93 | ->sortable() 94 | ->toggleable(isToggledHiddenByDefault: true), 95 | ]) 96 | ->filters([ 97 | // 98 | ]) 99 | ->actions([ 100 | Tables\Actions\ViewAction::make(), 101 | Tables\Actions\EditAction::make(), 102 | ]) 103 | ->bulkActions([ 104 | Tables\Actions\BulkActionGroup::make([ 105 | Tables\Actions\DeleteBulkAction::make(), 106 | ]), 107 | ]); 108 | } 109 | 110 | public static function getRelations(): array 111 | { 112 | return [ 113 | // 114 | ]; 115 | } 116 | 117 | public static function getPages(): array 118 | { 119 | return [ 120 | 'index' => Pages\ListModules::route('/'), 121 | 'create' => Pages\CreateModule::route('/create'), 122 | 'view' => Pages\ViewModule::route('/{record}'), 123 | 'edit' => Pages\EditModule::route('/{record}/edit'), 124 | ]; 125 | } 126 | } 127 | -------------------------------------------------------------------------------- /config/database.php: -------------------------------------------------------------------------------- 1 | env('DB_CONNECTION', 'mysql'), 19 | 20 | /* 21 | |-------------------------------------------------------------------------- 22 | | Database Connections 23 | |-------------------------------------------------------------------------- 24 | | 25 | | Here are each of the database connections setup for your application. 26 | | Of course, examples of configuring each database platform that is 27 | | supported by Laravel is shown below to make development simple. 28 | | 29 | | 30 | | All database work in Laravel is done through the PHP PDO facilities 31 | | so make sure you have the driver for your particular database of 32 | | choice installed on your machine before you begin development. 33 | | 34 | */ 35 | 36 | 'connections' => [ 37 | 38 | 'sqlite' => [ 39 | 'driver' => 'sqlite', 40 | 'url' => env('DATABASE_URL'), 41 | 'database' => env('DB_DATABASE', database_path('database.sqlite')), 42 | 'prefix' => '', 43 | 'foreign_key_constraints' => env('DB_FOREIGN_KEYS', true), 44 | ], 45 | 46 | 'mysql' => [ 47 | 'driver' => 'mysql', 48 | 'url' => env('DATABASE_URL'), 49 | 'host' => env('DB_HOST', '127.0.0.1'), 50 | 'port' => env('DB_PORT', '3306'), 51 | 'database' => env('DB_DATABASE', 'forge'), 52 | 'username' => env('DB_USERNAME', 'forge'), 53 | 'password' => env('DB_PASSWORD', ''), 54 | 'unix_socket' => env('DB_SOCKET', ''), 55 | 'charset' => 'utf8mb4', 56 | 'collation' => 'utf8mb4_unicode_ci', 57 | 'prefix' => '', 58 | 'prefix_indexes' => true, 59 | 'strict' => true, 60 | 'engine' => null, 61 | 'options' => extension_loaded('pdo_mysql') ? array_filter([ 62 | PDO::MYSQL_ATTR_SSL_CA => env('MYSQL_ATTR_SSL_CA'), 63 | ]) : [], 64 | ], 65 | 66 | 'pgsql' => [ 67 | 'driver' => 'pgsql', 68 | 'url' => env('DATABASE_URL'), 69 | 'host' => env('DB_HOST', '127.0.0.1'), 70 | 'port' => env('DB_PORT', '5432'), 71 | 'database' => env('DB_DATABASE', 'forge'), 72 | 'username' => env('DB_USERNAME', 'forge'), 73 | 'password' => env('DB_PASSWORD', ''), 74 | 'charset' => 'utf8', 75 | 'prefix' => '', 76 | 'prefix_indexes' => true, 77 | 'search_path' => 'public', 78 | 'sslmode' => 'prefer', 79 | ], 80 | 81 | 'sqlsrv' => [ 82 | 'driver' => 'sqlsrv', 83 | 'url' => env('DATABASE_URL'), 84 | 'host' => env('DB_HOST', 'localhost'), 85 | 'port' => env('DB_PORT', '1433'), 86 | 'database' => env('DB_DATABASE', 'forge'), 87 | 'username' => env('DB_USERNAME', 'forge'), 88 | 'password' => env('DB_PASSWORD', ''), 89 | 'charset' => 'utf8', 90 | 'prefix' => '', 91 | 'prefix_indexes' => true, 92 | // 'encrypt' => env('DB_ENCRYPT', 'yes'), 93 | // 'trust_server_certificate' => env('DB_TRUST_SERVER_CERTIFICATE', 'false'), 94 | ], 95 | 96 | ], 97 | 98 | /* 99 | |-------------------------------------------------------------------------- 100 | | Migration Repository Table 101 | |-------------------------------------------------------------------------- 102 | | 103 | | This table keeps track of all the migrations that have already run for 104 | | your application. Using this information, we can determine which of 105 | | the migrations on disk haven't actually been run in the database. 106 | | 107 | */ 108 | 109 | 'migrations' => 'migrations', 110 | 111 | /* 112 | |-------------------------------------------------------------------------- 113 | | Redis Databases 114 | |-------------------------------------------------------------------------- 115 | | 116 | | Redis is an open source, fast, and advanced key-value store that also 117 | | provides a richer body of commands than a typical key-value system 118 | | such as APC or Memcached. Laravel makes it easy to dig right in. 119 | | 120 | */ 121 | 122 | 'redis' => [ 123 | 124 | 'client' => env('REDIS_CLIENT', 'phpredis'), 125 | 126 | 'options' => [ 127 | 'cluster' => env('REDIS_CLUSTER', 'redis'), 128 | 'prefix' => env('REDIS_PREFIX', Str::slug(env('APP_NAME', 'laravel'), '_').'_database_'), 129 | ], 130 | 131 | 'default' => [ 132 | 'url' => env('REDIS_URL'), 133 | 'host' => env('REDIS_HOST', '127.0.0.1'), 134 | 'username' => env('REDIS_USERNAME'), 135 | 'password' => env('REDIS_PASSWORD'), 136 | 'port' => env('REDIS_PORT', '6379'), 137 | 'database' => env('REDIS_DB', '0'), 138 | ], 139 | 140 | 'cache' => [ 141 | 'url' => env('REDIS_URL'), 142 | 'host' => env('REDIS_HOST', '127.0.0.1'), 143 | 'username' => env('REDIS_USERNAME'), 144 | 'password' => env('REDIS_PASSWORD'), 145 | 'port' => env('REDIS_PORT', '6379'), 146 | 'database' => env('REDIS_CACHE_DB', '1'), 147 | ], 148 | 149 | ], 150 | 151 | ]; 152 | -------------------------------------------------------------------------------- /resources/views/components/mail/html/themes/default.css: -------------------------------------------------------------------------------- 1 | /* Base */ 2 | 3 | body, 4 | body *:not(html):not(style):not(br):not(tr):not(code) { 5 | box-sizing: border-box; 6 | font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif, 7 | 'Apple Color Emoji', 'Segoe UI Emoji', 'Segoe UI Symbol'; 8 | position: relative; 9 | } 10 | 11 | body { 12 | -webkit-text-size-adjust: none; 13 | background-color: #ffffff; 14 | color: #718096; 15 | height: 100%; 16 | line-height: 1.4; 17 | margin: 0; 18 | padding: 0; 19 | width: 100% !important; 20 | } 21 | 22 | p, 23 | ul, 24 | ol, 25 | blockquote { 26 | line-height: 1.4; 27 | text-align: left; 28 | } 29 | 30 | a { 31 | color: #3869d4; 32 | } 33 | 34 | a img { 35 | border: none; 36 | } 37 | 38 | /* Typography */ 39 | 40 | h1 { 41 | color: #3d4852; 42 | font-size: 18px; 43 | font-weight: bold; 44 | margin-top: 0; 45 | text-align: left; 46 | } 47 | 48 | h2 { 49 | font-size: 16px; 50 | font-weight: bold; 51 | margin-top: 0; 52 | text-align: left; 53 | } 54 | 55 | h3 { 56 | font-size: 14px; 57 | font-weight: bold; 58 | margin-top: 0; 59 | text-align: left; 60 | } 61 | 62 | p { 63 | font-size: 16px; 64 | line-height: 1.5em; 65 | margin-top: 0; 66 | text-align: left; 67 | } 68 | 69 | p.sub { 70 | font-size: 12px; 71 | } 72 | 73 | img { 74 | max-width: 100%; 75 | } 76 | 77 | /* Layout */ 78 | 79 | .wrapper { 80 | -premailer-cellpadding: 0; 81 | -premailer-cellspacing: 0; 82 | -premailer-width: 100%; 83 | background-color: #edf2f7; 84 | margin: 0; 85 | padding: 0; 86 | width: 100%; 87 | } 88 | 89 | .content { 90 | -premailer-cellpadding: 0; 91 | -premailer-cellspacing: 0; 92 | -premailer-width: 100%; 93 | margin: 0; 94 | padding: 0; 95 | width: 100%; 96 | } 97 | 98 | /* Header */ 99 | 100 | .header { 101 | padding: 25px 0; 102 | text-align: center; 103 | } 104 | 105 | .header a { 106 | color: #3d4852; 107 | font-size: 19px; 108 | font-weight: bold; 109 | text-decoration: none; 110 | } 111 | 112 | /* Logo */ 113 | 114 | .logo { 115 | height: 75px; 116 | max-height: 75px; 117 | width: 75px; 118 | } 119 | 120 | /* Body */ 121 | 122 | .body { 123 | -premailer-cellpadding: 0; 124 | -premailer-cellspacing: 0; 125 | -premailer-width: 100%; 126 | background-color: #edf2f7; 127 | border-bottom: 1px solid #edf2f7; 128 | border-top: 1px solid #edf2f7; 129 | margin: 0; 130 | padding: 0; 131 | width: 100%; 132 | } 133 | 134 | .inner-body { 135 | -premailer-cellpadding: 0; 136 | -premailer-cellspacing: 0; 137 | -premailer-width: 570px; 138 | background-color: #ffffff; 139 | border-color: #e8e5ef; 140 | border-radius: 2px; 141 | border-width: 1px; 142 | box-shadow: 0 2px 0 rgba(0, 0, 150, 0.025), 2px 4px 0 rgba(0, 0, 150, 0.015); 143 | margin: 0 auto; 144 | padding: 0; 145 | width: 570px; 146 | } 147 | 148 | /* Subcopy */ 149 | 150 | .subcopy { 151 | border-top: 1px solid #e8e5ef; 152 | margin-top: 25px; 153 | padding-top: 25px; 154 | } 155 | 156 | .subcopy p { 157 | font-size: 14px; 158 | } 159 | 160 | /* Footer */ 161 | 162 | .footer { 163 | -premailer-cellpadding: 0; 164 | -premailer-cellspacing: 0; 165 | -premailer-width: 570px; 166 | margin: 0 auto; 167 | padding: 0; 168 | text-align: center; 169 | width: 570px; 170 | } 171 | 172 | .footer p { 173 | color: #b0adc5; 174 | font-size: 12px; 175 | text-align: center; 176 | } 177 | 178 | .footer a { 179 | color: #b0adc5; 180 | text-decoration: underline; 181 | } 182 | 183 | /* Tables */ 184 | 185 | .table table { 186 | -premailer-cellpadding: 0; 187 | -premailer-cellspacing: 0; 188 | -premailer-width: 100%; 189 | margin: 30px auto; 190 | width: 100%; 191 | } 192 | 193 | .table th { 194 | border-bottom: 1px solid #edeff2; 195 | margin: 0; 196 | padding-bottom: 8px; 197 | } 198 | 199 | .table td { 200 | color: #74787e; 201 | font-size: 15px; 202 | line-height: 18px; 203 | margin: 0; 204 | padding: 10px 0; 205 | } 206 | 207 | .content-cell { 208 | max-width: 100vw; 209 | padding: 32px; 210 | } 211 | 212 | /* Buttons */ 213 | 214 | .action { 215 | -premailer-cellpadding: 0; 216 | -premailer-cellspacing: 0; 217 | -premailer-width: 100%; 218 | margin: 30px auto; 219 | padding: 0; 220 | text-align: center; 221 | width: 100%; 222 | } 223 | 224 | .button { 225 | -webkit-text-size-adjust: none; 226 | border-radius: 4px; 227 | color: #fff; 228 | display: inline-block; 229 | overflow: hidden; 230 | text-decoration: none; 231 | } 232 | 233 | .button-blue, 234 | .button-primary { 235 | background-color: #2d3748; 236 | border-bottom: 8px solid #2d3748; 237 | border-left: 18px solid #2d3748; 238 | border-right: 18px solid #2d3748; 239 | border-top: 8px solid #2d3748; 240 | } 241 | 242 | .button-green, 243 | .button-success { 244 | background-color: #48bb78; 245 | border-bottom: 8px solid #48bb78; 246 | border-left: 18px solid #48bb78; 247 | border-right: 18px solid #48bb78; 248 | border-top: 8px solid #48bb78; 249 | } 250 | 251 | .button-red, 252 | .button-error { 253 | background-color: #e53e3e; 254 | border-bottom: 8px solid #e53e3e; 255 | border-left: 18px solid #e53e3e; 256 | border-right: 18px solid #e53e3e; 257 | border-top: 8px solid #e53e3e; 258 | } 259 | 260 | /* Panels */ 261 | 262 | .panel { 263 | border-left: #2d3748 solid 4px; 264 | margin: 21px 0; 265 | } 266 | 267 | .panel-content { 268 | background-color: #edf2f7; 269 | color: #718096; 270 | padding: 16px; 271 | } 272 | 273 | .panel-content p { 274 | color: #718096; 275 | } 276 | 277 | .panel-item { 278 | padding: 0; 279 | } 280 | 281 | .panel-item p:last-of-type { 282 | margin-bottom: 0; 283 | padding-bottom: 0; 284 | } 285 | 286 | /* Utilities */ 287 | 288 | .break-all { 289 | word-break: break-all; 290 | } 291 | -------------------------------------------------------------------------------- /public/assets/head-filled.svg: -------------------------------------------------------------------------------- 1 | 2 | banners 3 | 4 | 5 | 6 | 8 | 9 | 10 | 11 | --------------------------------------------------------------------------------