diff --git a/app/Http/Controllers/ToolController.php b/app/Http/Controllers/ToolController.php index eb8c3a2..d0a6454 100644 --- a/app/Http/Controllers/ToolController.php +++ b/app/Http/Controllers/ToolController.php @@ -129,6 +129,24 @@ public function index(): View 'route' => 'tools.cron', 'icon' => 'clock', ], + [ + 'name' => 'JWT Decoder', + 'description' => 'Decode and inspect JSON Web Tokens', + 'route' => 'tools.jwt', + 'icon' => 'jwt', + ], + [ + 'name' => 'Timestamp Converter', + 'description' => 'Convert Unix timestamps to dates and vice versa', + 'route' => 'tools.timestamp', + 'icon' => 'timestamp', + ], + [ + 'name' => 'Diff Checker', + 'description' => 'Compare two texts and highlight differences', + 'route' => 'tools.diff', + 'icon' => 'diff', + ], ]; return view('home', compact('tools')); @@ -233,4 +251,19 @@ public function cron(): View { return view('tools.cron-parser'); } + + public function jwt(): View + { + return view('tools.jwt'); + } + + public function timestamp(): View + { + return view('tools.timestamp'); + } + + public function diff(): View + { + return view('tools.diff'); + } } diff --git a/resources/views/home.blade.php b/resources/views/home.blade.php index 2a21f80..4e1851a 100644 --- a/resources/views/home.blade.php +++ b/resources/views/home.blade.php @@ -90,7 +90,6 @@ @break - @case('calculator') @@ -137,6 +136,21 @@ @break + @case('jwt') + + + + @break + @case('timestamp') + + + + @break + @case('diff') + + + + @break @endswitch
diff --git a/resources/views/tools/diff.blade.php b/resources/views/tools/diff.blade.php new file mode 100644 index 0000000..3b2869c --- /dev/null +++ b/resources/views/tools/diff.blade.php @@ -0,0 +1,336 @@ +@extends('layouts.app') + +@section('title', 'Diff Checker - Compare Text Online | Dev Tools') +@section('meta_description', 'Free online diff checker. Compare two texts side by side, highlight differences, and see additions, deletions, and changes. Perfect for code review.') +@section('meta_keywords', 'diff checker, text compare, diff tool, compare files, code diff, text difference, online diff') + +@push('schema') + +@endpush + +@section('content') +
+
+
+

Diff Checker

+

Compare two texts and highlight differences

+
+ ← Back +
+ + +
+
+
+ + +
+ +
+ +
+
+ + +
+ +
+
+ + +
+
+ + + +
+ +
+ + + +
+
+ + +
+
+ added +
+
+ removed +
+
+ unchanged +
+
+ + +
+
+
+ Original +
+
+ Modified +
+
+
+
+ +
+ +
+ +
+ +
+
+
+
+ + +
+ + + +

Enter text in both fields and click Compare to see differences

+
+
+@endsection + +@push('scripts') + +@endpush diff --git a/resources/views/tools/jwt.blade.php b/resources/views/tools/jwt.blade.php new file mode 100644 index 0000000..4910a1b --- /dev/null +++ b/resources/views/tools/jwt.blade.php @@ -0,0 +1,336 @@ +@extends('layouts.app') + +@section('title', 'JWT Decoder - Decode JSON Web Tokens Online | Dev Tools') +@section('meta_description', 'Free online JWT decoder. Decode and inspect JSON Web Tokens, view header and payload, check expiration status. No data sent to server.') +@section('meta_keywords', 'jwt decoder, json web token, jwt parser, jwt debugger, decode jwt, jwt viewer, token decoder, jwt online') + +@push('schema') + +@endpush + +@section('content') +
+
+
+

JWT Decoder

+

Decode and inspect JSON Web Tokens

+
+ ← Back +
+ +
+ +
+
+
+ + +
+ +
+
+ + +
+ +
+ .. +
+
+
+ + Header +
+
+ + Payload +
+
+ + Signature +
+
+
+ + +
+
+ + + +
+

Security Note

+

JWTs are decoded client-side. No data is sent to any server. Never share tokens containing sensitive information.

+
+
+
+
+ + +
+ +
+
+ + + +
+

+
+ + +
+
+ + +
+

+                
Paste a JWT to see the header
+
+ + +
+
+ + +
+

+                
Paste a JWT to see the payload
+
+ + +
+ +
+ +
+
+
+
+
+@endsection + +@push('scripts') + +@endpush diff --git a/resources/views/tools/timestamp.blade.php b/resources/views/tools/timestamp.blade.php new file mode 100644 index 0000000..7f78f80 --- /dev/null +++ b/resources/views/tools/timestamp.blade.php @@ -0,0 +1,383 @@ +@extends('layouts.app') + +@section('title', 'Unix Timestamp Converter - Convert Timestamps Online | Dev Tools') +@section('meta_description', 'Free online Unix timestamp converter. Convert timestamps to human-readable dates and vice versa. Supports seconds, milliseconds, and multiple timezones.') +@section('meta_keywords', 'unix timestamp, timestamp converter, epoch converter, date to timestamp, timestamp to date, unix time, epoch time') + +@push('schema') + +@endpush + +@section('content') +
+
+
+

Timestamp Converter

+

Convert Unix timestamps to dates and vice versa

+
+ ← Back +
+ + +
+
+

Current Unix Timestamp

+
+ + +
+

+
+
+ +
+ +
+

Timestamp to Date

+ +
+
+ + +
+ +
+ + +
+ +
+ +
+
+ Local Time + +
+
+ UTC + +
+
+ ISO 8601 + +
+
+ Relative + +
+
+
+
+ + +
+

Date to Timestamp

+ +
+
+
+ + +
+
+ + +
+
+ +
+ + +
+ + + +
+
+ Seconds +
+ + +
+
+
+ Milliseconds +
+ + +
+
+
+
+
+
+ + +
+

Quick Reference

+
+ +
+
+
+@endsection + +@push('scripts') + +@endpush diff --git a/routes/web.php b/routes/web.php index ee8cb4e..63e04f2 100644 --- a/routes/web.php +++ b/routes/web.php @@ -17,18 +17,18 @@ Route::get('/url', [ToolController::class, 'url'])->name('url'); Route::get('/code-editor', [ToolController::class, 'codeEditor'])->name('code-editor'); Route::get('/regex', [ToolController::class, 'regex'])->name('regex'); - Route::get('/base-converter', [ToolController::class, 'baseConverter'])->name('base-converter'); Route::get('/slug-generator', [ToolController::class, 'slugGenerator'])->name('slug-generator'); Route::get('/color-picker', [ToolController::class, 'colorPicker'])->name('color-picker'); - Route::get('/qr-code', [ToolController::class, 'qrCode'])->name('qr-code'); - Route::get('/html-entity', [ToolController::class, 'htmlEntity'])->name('html-entity'); Route::get('/text-case', [ToolController::class, 'textCase'])->name('text-case'); Route::get('/password', [ToolController::class, 'password'])->name('password'); Route::get('/lorem', [ToolController::class, 'lorem'])->name('lorem'); Route::get('/cron', [ToolController::class, 'cron'])->name('cron'); + Route::get('/jwt', [ToolController::class, 'jwt'])->name('jwt'); + Route::get('/timestamp', [ToolController::class, 'timestamp'])->name('timestamp'); + Route::get('/diff', [ToolController::class, 'diff'])->name('diff'); }); // Static Pages @@ -50,18 +50,18 @@ ['loc' => route('tools.url'), 'priority' => '0.8', 'changefreq' => 'monthly'], ['loc' => route('tools.code-editor'), 'priority' => '0.9', 'changefreq' => 'monthly'], ['loc' => route('tools.regex'), 'priority' => '0.8', 'changefreq' => 'monthly'], - ['loc' => route('tools.base-converter'), 'priority' => '0.8', 'changefreq' => 'monthly'], ['loc' => route('tools.slug-generator'), 'priority' => '0.8', 'changefreq' => 'monthly'], ['loc' => route('tools.color-picker'), 'priority' => '0.8', 'changefreq' => 'monthly'], - ['loc' => route('tools.qr-code'), 'priority' => '0.8', 'changefreq' => 'monthly'], - ['loc' => route('tools.html-entity'), 'priority' => '0.8', 'changefreq' => 'monthly'], ['loc' => route('tools.text-case'), 'priority' => '0.8', 'changefreq' => 'monthly'], ['loc' => route('tools.password'), 'priority' => '0.8', 'changefreq' => 'monthly'], ['loc' => route('tools.lorem'), 'priority' => '0.8', 'changefreq' => 'monthly'], ['loc' => route('tools.cron'), 'priority' => '0.8', 'changefreq' => 'monthly'], + ['loc' => route('tools.jwt'), 'priority' => '0.8', 'changefreq' => 'monthly'], + ['loc' => route('tools.timestamp'), 'priority' => '0.8', 'changefreq' => 'monthly'], + ['loc' => route('tools.diff'), 'priority' => '0.8', 'changefreq' => 'monthly'], ['loc' => route('about'), 'priority' => '0.5', 'changefreq' => 'monthly'], ['loc' => route('privacy'), 'priority' => '0.3', 'changefreq' => 'yearly'], ]; diff --git a/tests/Feature/WebRoutesTest.php b/tests/Feature/WebRoutesTest.php index ae1bc1b..d99e52e 100644 --- a/tests/Feature/WebRoutesTest.php +++ b/tests/Feature/WebRoutesTest.php @@ -29,18 +29,18 @@ public function test_home_page_displays_all_tools(): void $response->assertSee('URL Encoder'); $response->assertSee('Code Editor'); $response->assertSee('Regex Tester'); - $response->assertSee('Base Converter'); $response->assertSee('Slug Generator'); $response->assertSee('Color Picker'); - $response->assertSee('QR Code Generator'); - $response->assertSee('HTML Entity Encoder'); $response->assertSee('Text Case Converter'); $response->assertSee('Password Generator'); $response->assertSee('Lorem Ipsum Generator'); $response->assertSee('Cron Parser'); + $response->assertSee('JWT Decoder'); + $response->assertSee('Timestamp Converter'); + $response->assertSee('Diff Checker'); } public function test_home_page_has_tool_links(): void @@ -58,18 +58,18 @@ public function test_home_page_has_tool_links(): void $response->assertSee('href="' . route('tools.url') . '"', false); $response->assertSee('href="' . route('tools.code-editor') . '"', false); $response->assertSee('href="' . route('tools.regex') . '"', false); - $response->assertSee('href="' . route('tools.base-converter') . '"', false); $response->assertSee('href="' . route('tools.slug-generator') . '"', false); $response->assertSee('href="' . route('tools.color-picker') . '"', false); - $response->assertSee('href="' . route('tools.qr-code') . '"', false); - $response->assertSee('href="' . route('tools.html-entity') . '"', false); $response->assertSee('href="' . route('tools.text-case') . '"', false); $response->assertSee('href="' . route('tools.password') . '"', false); $response->assertSee('href="' . route('tools.lorem') . '"', false); $response->assertSee('href="' . route('tools.cron') . '"', false); + $response->assertSee('href="' . route('tools.jwt') . '"', false); + $response->assertSee('href="' . route('tools.timestamp') . '"', false); + $response->assertSee('href="' . route('tools.diff') . '"', false); } public function test_csv_tool_page_loads(): void @@ -458,9 +458,67 @@ public function test_cron_tool_has_required_elements(): void $response->assertSee('Next Run Times'); } + public function test_jwt_tool_page_loads(): void + { + $response = $this->get('/tools/jwt'); + + $response->assertStatus(200); + $response->assertSee('JWT Decoder'); + $response->assertSee('Decode and inspect JSON Web Tokens'); + } + + public function test_jwt_tool_has_required_elements(): void + { + $response = $this->get('/tools/jwt'); + + $response->assertStatus(200); + $response->assertSee('JWT Token'); + $response->assertSee('Header'); + $response->assertSee('Payload'); + $response->assertSee('Load sample'); + } + + public function test_timestamp_tool_page_loads(): void + { + $response = $this->get('/tools/timestamp'); + + $response->assertStatus(200); + $response->assertSee('Timestamp Converter'); + $response->assertSee('Convert Unix timestamps to dates'); + } + + public function test_timestamp_tool_has_required_elements(): void + { + $response = $this->get('/tools/timestamp'); + + $response->assertStatus(200); + $response->assertSee('Timestamp to Date'); + $response->assertSee('Date to Timestamp'); + $response->assertSee('Current Unix Timestamp'); + } + + public function test_diff_tool_page_loads(): void + { + $response = $this->get('/tools/diff'); + + $response->assertStatus(200); + $response->assertSee('Diff Checker'); + $response->assertSee('Compare two texts and highlight differences'); + } + + public function test_diff_tool_has_required_elements(): void + { + $response = $this->get('/tools/diff'); + + $response->assertStatus(200); + $response->assertSee('Original Text'); + $response->assertSee('Modified Text'); + $response->assertSee('Compare'); + } + public function test_all_pages_have_navigation(): void { - $pages = ['/', '/tools/csv', '/tools/yaml', '/tools/markdown', '/tools/sql', '/tools/base64', '/tools/uuid', '/tools/hash', '/tools/url', '/tools/code-editor', '/tools/regex', '/tools/base-converter', '/tools/slug-generator', '/tools/color-picker', '/tools/qr-code', '/tools/html-entity', '/tools/text-case', '/tools/password', '/tools/lorem', '/tools/cron']; + $pages = ['/', '/tools/csv', '/tools/yaml', '/tools/markdown', '/tools/sql', '/tools/base64', '/tools/uuid', '/tools/hash', '/tools/url', '/tools/code-editor', '/tools/regex', '/tools/base-converter', '/tools/slug-generator', '/tools/color-picker', '/tools/qr-code', '/tools/html-entity', '/tools/text-case', '/tools/password', '/tools/lorem', '/tools/cron', '/tools/jwt', '/tools/timestamp', '/tools/diff']; foreach ($pages as $page) { $response = $this->get($page); @@ -472,7 +530,7 @@ public function test_all_pages_have_navigation(): void public function test_all_pages_have_theme_toggle(): void { - $pages = ['/', '/tools/csv', '/tools/yaml', '/tools/markdown', '/tools/sql', '/tools/base64', '/tools/uuid', '/tools/hash', '/tools/url', '/tools/code-editor', '/tools/regex', '/tools/base-converter', '/tools/slug-generator', '/tools/color-picker', '/tools/qr-code', '/tools/html-entity', '/tools/text-case', '/tools/password', '/tools/lorem', '/tools/cron']; + $pages = ['/', '/tools/csv', '/tools/yaml', '/tools/markdown', '/tools/sql', '/tools/base64', '/tools/uuid', '/tools/hash', '/tools/url', '/tools/code-editor', '/tools/regex', '/tools/base-converter', '/tools/slug-generator', '/tools/color-picker', '/tools/qr-code', '/tools/html-entity', '/tools/text-case', '/tools/password', '/tools/lorem', '/tools/cron', '/tools/jwt', '/tools/timestamp', '/tools/diff']; foreach ($pages as $page) { $response = $this->get($page); @@ -485,7 +543,7 @@ public function test_all_pages_have_theme_toggle(): void public function test_all_pages_load_vite_assets(): void { // Code editor uses standalone template without Vite - $pages = ['/', '/tools/csv', '/tools/yaml', '/tools/markdown', '/tools/sql', '/tools/base64', '/tools/uuid', '/tools/hash', '/tools/url', '/tools/regex', '/tools/base-converter', '/tools/slug-generator', '/tools/color-picker', '/tools/qr-code', '/tools/html-entity', '/tools/text-case', '/tools/password', '/tools/lorem', '/tools/cron']; + $pages = ['/', '/tools/csv', '/tools/yaml', '/tools/markdown', '/tools/sql', '/tools/base64', '/tools/uuid', '/tools/hash', '/tools/url', '/tools/regex', '/tools/base-converter', '/tools/slug-generator', '/tools/color-picker', '/tools/qr-code', '/tools/html-entity', '/tools/text-case', '/tools/password', '/tools/lorem', '/tools/cron', '/tools/jwt', '/tools/timestamp', '/tools/diff']; foreach ($pages as $page) { $response = $this->get($page); @@ -498,7 +556,7 @@ public function test_all_pages_load_vite_assets(): void public function test_all_tool_pages_have_back_link(): void { // Code editor uses standalone template with home link instead of back - $toolPages = ['/tools/csv', '/tools/yaml', '/tools/markdown', '/tools/sql', '/tools/base64', '/tools/uuid', '/tools/hash', '/tools/url', '/tools/regex', '/tools/base-converter', '/tools/slug-generator', '/tools/color-picker', '/tools/qr-code', '/tools/html-entity', '/tools/text-case', '/tools/password', '/tools/lorem', '/tools/cron']; + $toolPages = ['/tools/csv', '/tools/yaml', '/tools/markdown', '/tools/sql', '/tools/base64', '/tools/uuid', '/tools/hash', '/tools/url', '/tools/regex', '/tools/base-converter', '/tools/slug-generator', '/tools/color-picker', '/tools/qr-code', '/tools/html-entity', '/tools/text-case', '/tools/password', '/tools/lorem', '/tools/cron', '/tools/jwt', '/tools/timestamp', '/tools/diff']; foreach ($toolPages as $page) { $response = $this->get($page); @@ -551,7 +609,7 @@ public function test_api_routes_reject_get_requests(): void public function test_pages_have_csrf_token(): void { - $pages = ['/tools/csv', '/tools/yaml', '/tools/markdown', '/tools/sql', '/tools/base64', '/tools/uuid', '/tools/hash', '/tools/url', '/tools/code-editor', '/tools/base-converter', '/tools/slug-generator', '/tools/color-picker', '/tools/qr-code', '/tools/html-entity', '/tools/text-case', '/tools/password', '/tools/lorem', '/tools/cron']; + $pages = ['/tools/csv', '/tools/yaml', '/tools/markdown', '/tools/sql', '/tools/base64', '/tools/uuid', '/tools/hash', '/tools/url', '/tools/code-editor', '/tools/regex', '/tools/base-converter', '/tools/slug-generator', '/tools/color-picker', '/tools/qr-code', '/tools/html-entity', '/tools/text-case', '/tools/password', '/tools/lorem', '/tools/cron', '/tools/jwt', '/tools/timestamp', '/tools/diff']; foreach ($pages as $page) { $response = $this->get($page);