diff --git a/app/Http/Controllers/ProjectController.php b/app/Http/Controllers/ProjectController.php index 6ec8405..06e9e90 100644 --- a/app/Http/Controllers/ProjectController.php +++ b/app/Http/Controllers/ProjectController.php @@ -53,6 +53,10 @@ public function page_index(Request $request) return view('project.index', [ 'projects' => Project::paginate(10), 'statistics' => Project::allStatistics(), + ...(!$request->project_id ? [ + 'onboarding_status' => $request->get('onboarding_status'), + 'onboarding_dismissed' => !empty(Auth::user()->onboarding_dismissed_at), + ] : []), ]); } diff --git a/app/Http/Controllers/UserController.php b/app/Http/Controllers/UserController.php index e19a872..ae89a61 100644 --- a/app/Http/Controllers/UserController.php +++ b/app/Http/Controllers/UserController.php @@ -6,6 +6,7 @@ use App\Helpers\PermissionSet; use App\Models\User; +use Carbon\Carbon; use Illuminate\Http\Request; use Illuminate\Support\Facades\Artisan; use Illuminate\Support\Facades\Auth; @@ -161,4 +162,18 @@ public function action_delete(string $user_id) return redirect()->back()->with('warning', 'Ooops, something went wrong.'); } + + /** + * Dismiss the onboarding. + * + * @return \Illuminate\Http\RedirectResponse + */ + public function action_dismiss_onboarding() + { + Auth::user()->update([ + 'onboarding_dismissed_at' => Carbon::now(), + ]); + + return redirect()->route('project.index')->with('success', 'Onboarding dismissed successfully.'); + } } diff --git a/app/Http/Middleware/IdentifyOnboardingStatus.php b/app/Http/Middleware/IdentifyOnboardingStatus.php new file mode 100644 index 0000000..c0f1ffc --- /dev/null +++ b/app/Http/Middleware/IdentifyOnboardingStatus.php @@ -0,0 +1,51 @@ + + */ +class IdentifyOnboardingStatus +{ + /** + * Handle an incoming request. + * + * @param Request $request + * @param Closure $next + * + * @return mixed + */ + public function handle(Request $request, Closure $next) + { + $firstProject = Project::first(); + $hasProjects = !empty($firstProject); + $hasClusterProvisionerTemplates = Template::where('type', '=', 'cluster')->count() > 0; + $hasClusters = Cluster::query()->count() > 0; + $hasApplicationTemplates = Template::where('type', '=', 'application')->count() > 0; + $hasApplications = Deployment::query()->count() > 0; + + $request->attributes->add(['onboarding_status' => (object) [ + 'projects' => $hasProjects, + 'cluster_provisioner_templates' => $hasClusterProvisionerTemplates, + 'clusters' => $hasClusters, + 'application_templates' => $hasApplicationTemplates, + 'applications' => $hasApplications, + 'first_project' => $firstProject, + ]]); + + return $next($request); + } +} diff --git a/app/Models/User.php b/app/Models/User.php index b6bb00d..4b82ad6 100644 --- a/app/Models/User.php +++ b/app/Models/User.php @@ -31,6 +31,7 @@ * @OA\Property(property="email_verified_at", type="string", format="date-time", example="2021-01-01T00:00:00Z", nullable=true), * @OA\Property(property="password", type="string", example="password123"), * @OA\Property(property="remember_token", type="string", example="remember_token123", nullable=true), + * @OA\Property(property="onboarding_dismissed_at", type="string", format="date-time", example="2021-01-01 00:00:00", nullable=true), * @OA\Property(property="created_at", type="string", format="date-time", example="2021-01-01 00:00:00", nullable=true), * @OA\Property(property="updated_at", type="string", format="date-time", example="2021-01-01 00:00:00", nullable=true), * ) @@ -51,7 +52,7 @@ * @property string $email * @property string $password * @property string $remember_token - * @property string $email_verified_at + * @property string $onboarding_dismissed_at * @property string $created_at * @property string $updated_at * @@ -85,6 +86,7 @@ class User extends Authenticatable implements JWTSubject 'name', 'email', 'password', + 'onboarding_dismissed_at', ]; /** @@ -105,8 +107,9 @@ class User extends Authenticatable implements JWTSubject protected function casts(): array { return [ - 'email_verified_at' => 'datetime', - 'password' => 'hashed', + 'email_verified_at' => 'datetime', + 'password' => 'hashed', + 'onboarding_dismissed_at' => 'datetime', ]; } diff --git a/database/migrations/2025_07_26_000001_update_user_tables.php b/database/migrations/2025_07_26_000001_update_user_tables.php new file mode 100644 index 0000000..ca2ca84 --- /dev/null +++ b/database/migrations/2025_07_26_000001_update_user_tables.php @@ -0,0 +1,29 @@ +timestamp('onboarding_dismissed_at')->nullable()->after('remember_token'); + }); + } + + /** + * Reverse the migrations. + */ + public function down() + { + Schema::table('users', function (Blueprint $table) { + $table->dropColumn('onboarding_dismissed_at'); + }); + } +}; diff --git a/resources/images/path.svg b/resources/images/path.svg new file mode 100644 index 0000000..5f9f413 --- /dev/null +++ b/resources/images/path.svg @@ -0,0 +1,12 @@ + + + diff --git a/resources/sass/app.scss b/resources/sass/app.scss index 578e444..1b62015 100644 --- a/resources/sass/app.scss +++ b/resources/sass/app.scss @@ -553,3 +553,48 @@ main { font-weight: 600; color: var(--bs-white); } + +.indicator-success { + position: absolute; + top: 0; + right: -0.5rem; + width: 1rem; + height: 1rem; + border-radius: 50%; + background-color: var(--bs-success); +} + +.indicator-warning { + position: absolute; + top: 0; + right: -0.5rem; + width: 1rem; + height: 1rem; + border-radius: 50%; + background-color: var(--bs-warning); +} + +.get-started { + > .card-body { + position: relative; + + &:after { + content: ''; + position: absolute; + bottom: 0; + right: 0; + width: 100%; + height: 100%; + background: url('../images/path.svg') no-repeat right center; + background-size: contain; + z-index: 1; + opacity: 0.25; + transform: scale(1.25); + } + + > * { + position: relative; + z-index: 2; + } + } +} diff --git a/resources/views/project/index.blade.php b/resources/views/project/index.blade.php index e94f34f..69d1a38 100644 --- a/resources/views/project/index.blade.php +++ b/resources/views/project/index.blade.php @@ -14,6 +14,129 @@