Skip to content

Commit

Permalink
feat: Added feature banner
Browse files Browse the repository at this point in the history
  • Loading branch information
lewislarsen committed Jul 30, 2024
1 parent 78db2b6 commit 6f780fc
Show file tree
Hide file tree
Showing 11 changed files with 493 additions and 4 deletions.
143 changes: 143 additions & 0 deletions app/Console/Commands/FetchNewFeatures.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,143 @@
<?php

declare(strict_types=1);

namespace App\Console\Commands;

use Exception;
use Illuminate\Console\Command;
use Illuminate\Contracts\Filesystem\FileNotFoundException;
use Illuminate\Http\Client\RequestException;
use Illuminate\Support\Facades\Cache;
use Illuminate\Support\Facades\File;
use Illuminate\Support\Facades\Http;
use Symfony\Component\Console\Command\Command as CommandAlias;

/**
* Command to fetch new features from GitHub or a local file and store them in cache.
*/
class FetchNewFeatures extends Command
{
protected $signature = 'vanguard:fetch-new-features {--local : Fetch from local new_features.json file}';

protected $description = 'Fetch new features from GitHub or local file and store in cache';

/**
* Handle the command execution.
*/
public function handle(): int
{
try {
$features = $this->fetchFeatures();

if ($features === []) {
$this->error('No new features found.');

return CommandAlias::FAILURE;
}

$this->storeLatestFeature($features);
$this->info('New features fetched and stored successfully.');

return CommandAlias::SUCCESS;
} catch (Exception $e) {
$this->error($e->getMessage());

return CommandAlias::FAILURE;
}
}

/**
* Fetch features from either local or remote source.
*
* @return array<int, array<string, string>>
*/
private function fetchFeatures(): array
{
return $this->option('local') ? $this->fetchLocal() : $this->fetchRemote();
}

/**
* Fetch features from the remote GitHub repository.
*
* @return array<int, array<string, string>>
*
* @throws Exception
*/
private function fetchRemote(): array
{
try {
$response = Http::get('https://raw.githubusercontent.com/vanguardbackup/vanguard/main/new_features.json');
$response->throw();

return $response->json();
} catch (RequestException $e) {
throw new Exception("Failed to fetch new features from remote: {$e->getMessage()}", $e->getCode(), $e);
}
}

/**
* Fetch features from the local file.
*
* @return array<int, array<string, string>>
*
* @throws Exception
*/
private function fetchLocal(): array
{
$path = base_path('new_features.json');

if (! File::exists($path)) {
throw new Exception('Local new_features.json file not found in project root.');
}

$content = File::get($path);
$features = json_decode($content, true);

if (json_last_error() !== JSON_ERROR_NONE || ! is_array($features)) {
throw new Exception('Failed to parse local new_features.json file.');
}

return $features;
}

/**
* Store the latest feature in the cache if available.
*
* @param array<int, array<string, string>> $features
*
* @throws FileNotFoundException
*/
private function storeLatestFeature(array $features): void
{
if ($features === []) {
$this->info('No features to store.');

return;
}

$latestFeature = end($features);
if ($latestFeature === false) {
$this->error('Failed to retrieve the latest feature.');

return;
}

$latestFeature['current_version'] = $this->getCurrentVersion();
$latestFeature['github_url'] ??= 'https://github.com/vanguardbackup/vanguard';

Cache::put('latest_feature', $latestFeature, now()->addDay());
}

/**
* Get the current version from the VERSION file.
*
* @throws FileNotFoundException
*/
private function getCurrentVersion(): string
{
$versionFile = base_path('VERSION');

return File::exists($versionFile) ? trim(File::get($versionFile)) : '0.0.0';
}
}
56 changes: 56 additions & 0 deletions app/Livewire/Other/NewFeatureBanner.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
<?php

declare(strict_types=1);

namespace App\Livewire\Other;

use Illuminate\Support\Facades\Cache;
use Illuminate\Support\Facades\Log;
use Illuminate\View\View;
use Livewire\Component;

/**
* Displays a banner for new features in the application.
* Fetches the latest feature from cache and allows users to dismiss it.
*/
class NewFeatureBanner extends Component
{
/**
* The latest feature to display in the banner.
*
* @var array<string, string>|null
*/
public ?array $latestFeature = null;

/**
* Initialize the component state.
*/
public function mount(): void
{
$cachedFeature = Cache::get('latest_feature');

if (is_array($cachedFeature)) {
$this->latestFeature = $cachedFeature;
} elseif ($cachedFeature !== null) {
Log::warning('Unexpected data type for latest_feature in cache', ['type' => gettype($cachedFeature)]);
$this->latestFeature = null;
}
}

/**
* Render the component.
*/
public function render(): View
{
return view('livewire.other.new-feature-banner');
}

/**
* Dismiss the feature banner.
*/
public function dismiss(): void
{
$this->latestFeature = null;
$this->dispatch('featureDismissed');
}
}
28 changes: 28 additions & 0 deletions bump_version.sh
Original file line number Diff line number Diff line change
Expand Up @@ -92,6 +92,27 @@ bump_version() {
echo "${parts[0]}.${parts[1]}.${parts[2]}"
}

update_feature_banner() {
local version="$1"
local title
local description

read -p "Enter the feature title: " title
read -p "Enter the feature description: " description

local json_content="[
{
\"title\": \"$title\",
\"description\": \"$description\",
\"version\": \"$version\",
\"github_url\": \"https://github.com/vanguardbackup/vanguard/releases/tag/$version\"
}
]"

echo "$json_content" > new_features.json
log "INFO" "Updated new_features.json with the latest feature information."
}

show_usage() {
echo "Usage: $0 [-v] <major|minor|patch>"
echo " -v: Enable verbose mode"
Expand Down Expand Up @@ -170,6 +191,13 @@ esac
log "INFO" "Bumping version to $NEW_VERSION ..."
echo "$NEW_VERSION" > VERSION

# Ask if the user wants to update the feature banner
read -p "Do you want to update the feature banner? (y/n): " update_banner
if [[ $update_banner =~ ^[Yy]$ ]]; then
update_feature_banner "$NEW_VERSION"
git add new_features.json
fi

log "INFO" "Committing version bump..."
git add VERSION
git commit --no-verify -m "chore: bump version from $OLD_VERSION to $NEW_VERSION 🎉"
Expand Down
8 changes: 8 additions & 0 deletions new_features.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
[
{
"title": "New Feature Banner!",
"description": "We have added a new feature banner to Vanguard.",
"version": "1.4.2",
"github_url": "https://github.com/vanguardbackup/vanguard/releases/tag/1.4.2"
}
]
2 changes: 1 addition & 1 deletion resources/views/account/partials/sidebar.blade.php
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
<nav class="bg-white dark:bg-gray-800 rounded-lg shadow-sm p-2 w-full">
<nav class="bg-white dark:bg-gray-800/50 rounded-[0.70rem] shadow-none border border-gray-200 dark:border-gray-800/30 p-2 w-full">
<ul class="flex lg:flex-col space-x-1 lg:space-x-0 lg:space-y-1 justify-around lg:justify-start">
<li class="flex-1 lg:flex-initial">
<x-sidebar-nav-link :href="route('profile')" :active="request()->routeIs('profile')" wire:navigate>
Expand Down
14 changes: 11 additions & 3 deletions resources/views/components/no-content.blade.php
Original file line number Diff line number Diff line change
@@ -1,9 +1,13 @@
@props(['icon', 'title', 'description', 'action' => null, 'withBackground' => false])

@if ($withBackground)
<div class="bg-white dark:bg-gray-800/50 dark:border-gray-800/30 rounded-[0.70rem] overflow-hidden border border-gray-950/5 shadow-none p-8 transition duration-300 ease-in-out hover:shadow-md">
<div class="bg-white dark:bg-gray-800/50 dark:border-gray-800/30 rounded-[0.70rem] overflow-hidden border border-gray-200 shadow-none p-8 transition duration-300 ease-in-out hover:shadow-md">
<div class="text-center my-10">
{{ $icon }}
<div class="flex justify-center mb-4">
<div class="inline-flex items-center justify-center w-28 h-28 rounded-full bg-primary-100 dark:bg-primary-800">
{{ $icon }}
</div>
</div>
<h3 class="text-gray-900 dark:text-white text-xl font-semibold my-4">
{{ $title }}
</h3>
Expand All @@ -19,7 +23,11 @@
</div>
@else
<div class="text-center my-10">
{{ $icon }}
<div class="flex justify-center mb-4">
<div class="inline-flex items-center justify-center w-16 h-16 rounded-full bg-primary-100 dark:bg-primary-800">
{{ $icon }}
</div>
</div>
<h3 class="text-gray-900 dark:text-white text-xl font-semibold my-4">
{{ $title }}
</h3>
Expand Down
1 change: 1 addition & 0 deletions resources/views/layouts/app.blade.php
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,7 @@
@endif
</head>
<body class="font-sans antialiased">
<livewire:other.new-feature-banner/>
@include('partials.missing-keys-and-passphrase')
<div class="min-h-screen bg-primary-100 dark:bg-gray-900">
<livewire:layout.navigation/>
Expand Down
69 changes: 69 additions & 0 deletions resources/views/livewire/other/new-feature-banner.blade.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
<div>
@php
$currentVersion = obtain_vanguard_version();
$featureVersion = $latestFeature['version'] ?? '0.0.0';
$showBanner = version_compare($currentVersion, $featureVersion, '>=');
@endphp

@if ($latestFeature && $showBanner)
<div
x-data="{
show: true,
copied: false,
copyFeatureDetails() {
const details = `${this.$refs.title.textContent}\n${this.$refs.description.textContent}`;
navigator.clipboard.writeText(details);
this.copied = true;
setTimeout(() => this.copied = false, 2000);
}
}"
x-show="show"
x-transition:enter="transition ease-out duration-300"
x-transition:enter-start="opacity-0 transform -translate-y-2"
x-transition:enter-end="opacity-100 transform translate-y-0"
x-transition:leave="transition ease-in duration-300"
x-transition:leave-start="opacity-100 transform translate-y-0"
x-transition:leave-end="opacity-0 transform -translate-y-2"
class="bg-gradient-to-r from-blue-500/90 to-blue-600/90 text-white relative shadow-lg"
role="alert"
aria-live="assertive"
>
<div class="max-w-7xl mx-auto py-4 px-4 sm:px-6 lg:px-8">
<div class="flex flex-col sm:flex-row items-center justify-between">
<div class="flex items-center w-full sm:w-auto mb-4 sm:mb-0">
<span class="flex p-2 rounded-full bg-blue-700/50 backdrop-blur-sm">
@svg('heroicon-o-sparkles', 'h-6 w-6 text-white')
</span>
<p class="ml-3 font-medium text-base sm:text-lg">
{{ __('New Feature!') }}
</p>
</div>
<div class="flex flex-col sm:flex-row items-stretch sm:items-center space-y-2 sm:space-y-0 sm:space-x-3 w-full sm:w-auto">
<div class="flex-grow sm:flex-grow-0">
<p x-ref="title" class="font-bold text-sm sm:text-base">{{ $latestFeature['title'] }}</p>
<p x-ref="description" class="text-sm text-white/80">{{ $latestFeature['description'] }}</p>
</div>
<div class="flex space-x-2">
<a
href="{{ $latestFeature['github_url'] ?? 'https://github.com/vanguardbackup/vanguard' }}"
target="_blank"
rel="noopener noreferrer"
class="bg-white/10 hover:bg-white/20 backdrop-blur-sm px-3 py-1 text-sm font-medium text-white rounded-full border border-white/25 focus:outline-none focus:ring-2 focus:ring-white/50 focus:ring-offset-2 focus:ring-offset-blue-600 transition-all duration-150 ease-out hover:shadow-lg hover:-translate-y-0.5"
>
@svg('heroicon-o-code-bracket', 'h-4 w-4 inline mr-1')
{{ __('View on GitHub') }}
</a>
<button
wire:click="dismiss"
@click="show = false"
class="bg-white/10 hover:bg-white/20 backdrop-blur-sm px-3 py-1 text-sm font-medium text-white rounded-full border border-white/25 focus:outline-none focus:ring-2 focus:ring-white/50 focus:ring-offset-2 focus:ring-offset-blue-600 transition-all duration-150 ease-out hover:shadow-lg hover:-translate-y-0.5"
>
{{ __('Dismiss') }}
</button>
</div>
</div>
</div>
</div>
</div>
@endif
</div>
4 changes: 4 additions & 0 deletions routes/console.php
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

use App\Console\Commands\EnsureConnectionToBackupDestinationsCommand;
use App\Console\Commands\ExecuteScheduledBackupTasksCommand;
use App\Console\Commands\FetchNewFeatures;
use App\Console\Commands\ResetInoperativeBackupTasksCommand;
use App\Console\Commands\SendSummaryBackupTaskEmails;
use App\Console\Commands\VerifyConnectionToRemoteServersCommand;
Expand All @@ -20,3 +21,6 @@

Schedule::command(SendSummaryBackupTaskEmails::class)
->mondays()->at('07:00');

Schedule::command(FetchNewFeatures::class)
->dailyAt('02:00');
Loading

0 comments on commit 6f780fc

Please sign in to comment.