removed old boilerplate, implemented S3 uploading.

This commit is contained in:
ak 2023-10-18 21:20:47 -07:00
parent d40584de14
commit c2ec2af33c
29 changed files with 615 additions and 395 deletions

View file

@ -1,3 +1,4 @@
# laravel-vue-file-share # laravel-vue-file-share
Dropbox/Google Drive-esque file sharing application implemented in Laravel, PHP, Inertia, Vue, Headless-UI and Tailwind CSS. Uses Mitt for firing events and passing data. Dropbox/Google Drive-esque file sharing application implemented in Laravel, PHP, Inertia, Vue, Headless-UI and Tailwind CSS. Uses Mitt for firing events and passing data.
Uses S3 for storing and retrieving files.

View file

@ -12,6 +12,8 @@ use App\Http\Requests\SharedFiles;
use App\Http\Resources\FileResource; use App\Http\Resources\FileResource;
use App\Jobs\UploadToS3;
use Illuminate\Http\Request; use Illuminate\Http\Request;
use Illuminate\Support\Facades\Auth; use Illuminate\Support\Facades\Auth;
@ -827,12 +829,16 @@ class FileController extends Controller
// loop over files, make and upload each one // loop over files, make and upload each one
foreach ($files as $userFile) { foreach ($files as $userFile) {
$file = new File(); $file = new File();
$file->stored_at = $userFile->store('/files/'.Auth::id()); $file->stored_at = $userFile->store('/files/' . Auth::id(), 'local');
$file->is_folder = false; $file->is_folder = false;
$file->name = $userFile->getClientOriginalName(); $file->name = $userFile->getClientOriginalName();
$file->mimetype = $userFile->getMimeType(); $file->mimetype = $userFile->getMimeType();
$file->size = $userFile->getSize(); $file->size = $userFile->getSize();
$file->s3 = 0; // file has NOT been uploaded to s3
$parent->appendNode($file); $parent->appendNode($file);
// upload file to S3
UploadToS3::dispatch($file);
} }
} }

61
app/Jobs/UploadToS3.php Normal file
View file

@ -0,0 +1,61 @@
<?php
namespace App\Jobs;
use Illuminate\Bus\Queueable;
use Illuminate\Contracts\Queue\ShouldBeUnique;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Foundation\Bus\Dispatchable;
use Illuminate\Queue\InteractsWithQueue;
use Illuminate\Queue\SerializesModels;
use App\Models\File;
use Illuminate\Support\Facades\Storage;
use Illuminate\Support\Facades\Log;
class UploadToS3 implements ShouldQueue
{
use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;
/**
* Create a new job instance.
*/
public function __construct(protected File $file)
{
//
}
/**
* Execute the job.
*/
public function handle(): void
{
// get file from constructor
$file = $this->file;
// if the file has not been uploaded to S3
if (!$file->s3) {
$localPath = Storage::disk('local')->path($file->stored_at);
Log::debug("File at " . $localPath . " being uploaded to S3");
// upload file to S3
try {
// upload to S3 with the "stored_at" path. get file from 'local' disk at the "stored_at" path.
$stored = Storage::put($file->stored_at, Storage::disk('local')->get($file->stored_at));
// if storing is successful, change DB and output log message
if ($stored) {
Log::debug("File uploaded to S3");
$file->s3 = 1;
$file->save();
}
// else file storing on S3 was not successful.
else {
Log::error("File upload to S3 was unsuccessful");
}
}
catch (\Exception $exception) {
Log::error($exception->getMessage());
}
}
// else do nothing
}
}

View file

@ -88,7 +88,7 @@ class File extends Model
else $model->path = ''; else $model->path = '';
// append current file or folder name to path name // append current file or folder name to path name
$model->path = $model->path . Str::slug($model->name); $model->path = $model->path . $model->name;
}); });
// define delete function // define delete function

View file

@ -16,6 +16,8 @@
"laravel/framework": "^10.10", "laravel/framework": "^10.10",
"laravel/sanctum": "^3.2", "laravel/sanctum": "^3.2",
"laravel/tinker": "^2.8", "laravel/tinker": "^2.8",
"league/flysystem-aws-s3-v3": "^3.0",
"psr/http-message": "^1.0",
"tightenco/ziggy": "^1.0" "tightenco/ziggy": "^1.0"
}, },
"require-dev": { "require-dev": {

327
composer.lock generated
View file

@ -4,8 +4,157 @@
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
"This file is @generated automatically" "This file is @generated automatically"
], ],
"content-hash": "6befa31489c527cc8174c05d5165886a", "content-hash": "edbfbe62b4cb8f6882359a6168f6d6e6",
"packages": [ "packages": [
{
"name": "aws/aws-crt-php",
"version": "v1.2.2",
"source": {
"type": "git",
"url": "https://github.com/awslabs/aws-crt-php.git",
"reference": "2f1dc7b7eda080498be96a4a6d683a41583030e9"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/awslabs/aws-crt-php/zipball/2f1dc7b7eda080498be96a4a6d683a41583030e9",
"reference": "2f1dc7b7eda080498be96a4a6d683a41583030e9",
"shasum": ""
},
"require": {
"php": ">=5.5"
},
"require-dev": {
"phpunit/phpunit": "^4.8.35||^5.6.3||^9.5",
"yoast/phpunit-polyfills": "^1.0"
},
"suggest": {
"ext-awscrt": "Make sure you install awscrt native extension to use any of the functionality."
},
"type": "library",
"autoload": {
"classmap": [
"src/"
]
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"Apache-2.0"
],
"authors": [
{
"name": "AWS SDK Common Runtime Team",
"email": "aws-sdk-common-runtime@amazon.com"
}
],
"description": "AWS Common Runtime for PHP",
"homepage": "https://github.com/awslabs/aws-crt-php",
"keywords": [
"amazon",
"aws",
"crt",
"sdk"
],
"support": {
"issues": "https://github.com/awslabs/aws-crt-php/issues",
"source": "https://github.com/awslabs/aws-crt-php/tree/v1.2.2"
},
"time": "2023-07-20T16:49:55+00:00"
},
{
"name": "aws/aws-sdk-php",
"version": "3.283.7",
"source": {
"type": "git",
"url": "https://github.com/aws/aws-sdk-php.git",
"reference": "54cbb4253c6add698ec429bfb6ff2168c2fd7092"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/aws/aws-sdk-php/zipball/54cbb4253c6add698ec429bfb6ff2168c2fd7092",
"reference": "54cbb4253c6add698ec429bfb6ff2168c2fd7092",
"shasum": ""
},
"require": {
"aws/aws-crt-php": "^1.0.4",
"ext-json": "*",
"ext-pcre": "*",
"ext-simplexml": "*",
"guzzlehttp/guzzle": "^6.5.8 || ^7.4.5",
"guzzlehttp/promises": "^1.4.0 || ^2.0",
"guzzlehttp/psr7": "^1.9.1 || ^2.4.5",
"mtdowling/jmespath.php": "^2.6",
"php": ">=7.2.5",
"psr/http-message": "^1.0 || ^2.0"
},
"require-dev": {
"andrewsville/php-token-reflection": "^1.4",
"aws/aws-php-sns-message-validator": "~1.0",
"behat/behat": "~3.0",
"composer/composer": "^1.10.22",
"dms/phpunit-arraysubset-asserts": "^0.4.0",
"doctrine/cache": "~1.4",
"ext-dom": "*",
"ext-openssl": "*",
"ext-pcntl": "*",
"ext-sockets": "*",
"nette/neon": "^2.3",
"paragonie/random_compat": ">= 2",
"phpunit/phpunit": "^5.6.3 || ^8.5 || ^9.5",
"psr/cache": "^1.0",
"psr/simple-cache": "^1.0",
"sebastian/comparator": "^1.2.3 || ^4.0",
"yoast/phpunit-polyfills": "^1.0"
},
"suggest": {
"aws/aws-php-sns-message-validator": "To validate incoming SNS notifications",
"doctrine/cache": "To use the DoctrineCacheAdapter",
"ext-curl": "To send requests using cURL",
"ext-openssl": "Allows working with CloudFront private distributions and verifying received SNS messages",
"ext-sockets": "To use client-side monitoring"
},
"type": "library",
"extra": {
"branch-alias": {
"dev-master": "3.0-dev"
}
},
"autoload": {
"files": [
"src/functions.php"
],
"psr-4": {
"Aws\\": "src/"
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"Apache-2.0"
],
"authors": [
{
"name": "Amazon Web Services",
"homepage": "http://aws.amazon.com"
}
],
"description": "AWS SDK for PHP - Use Amazon Web Services in your PHP project",
"homepage": "http://aws.amazon.com/sdkforphp",
"keywords": [
"amazon",
"aws",
"cloud",
"dynamodb",
"ec2",
"glacier",
"s3",
"sdk"
],
"support": {
"forum": "https://forums.aws.amazon.com/forum.jspa?forumID=80",
"issues": "https://github.com/aws/aws-sdk-php/issues",
"source": "https://github.com/aws/aws-sdk-php/tree/3.283.7"
},
"time": "2023-10-18T20:16:37+00:00"
},
{ {
"name": "brick/math", "name": "brick/math",
"version": "0.11.0", "version": "0.11.0",
@ -1815,16 +1964,16 @@
}, },
{ {
"name": "league/flysystem", "name": "league/flysystem",
"version": "3.16.0", "version": "3.17.0",
"source": { "source": {
"type": "git", "type": "git",
"url": "https://github.com/thephpleague/flysystem.git", "url": "https://github.com/thephpleague/flysystem.git",
"reference": "4fdf372ca6b63c6e281b1c01a624349ccb757729" "reference": "bd4c9b26849d82364119c68429541f1631fba94b"
}, },
"dist": { "dist": {
"type": "zip", "type": "zip",
"url": "https://api.github.com/repos/thephpleague/flysystem/zipball/4fdf372ca6b63c6e281b1c01a624349ccb757729", "url": "https://api.github.com/repos/thephpleague/flysystem/zipball/bd4c9b26849d82364119c68429541f1631fba94b",
"reference": "4fdf372ca6b63c6e281b1c01a624349ccb757729", "reference": "bd4c9b26849d82364119c68429541f1631fba94b",
"shasum": "" "shasum": ""
}, },
"require": { "require": {
@ -1842,8 +1991,8 @@
"symfony/http-client": "<5.2" "symfony/http-client": "<5.2"
}, },
"require-dev": { "require-dev": {
"async-aws/s3": "^1.5", "async-aws/s3": "^1.5 || ^2.0",
"async-aws/simple-s3": "^1.1", "async-aws/simple-s3": "^1.1 || ^2.0",
"aws/aws-sdk-php": "^3.220.0", "aws/aws-sdk-php": "^3.220.0",
"composer/semver": "^3.0", "composer/semver": "^3.0",
"ext-fileinfo": "*", "ext-fileinfo": "*",
@ -1889,7 +2038,7 @@
], ],
"support": { "support": {
"issues": "https://github.com/thephpleague/flysystem/issues", "issues": "https://github.com/thephpleague/flysystem/issues",
"source": "https://github.com/thephpleague/flysystem/tree/3.16.0" "source": "https://github.com/thephpleague/flysystem/tree/3.17.0"
}, },
"funding": [ "funding": [
{ {
@ -1901,7 +2050,73 @@
"type": "github" "type": "github"
} }
], ],
"time": "2023-09-07T19:22:17+00:00" "time": "2023-10-05T20:15:05+00:00"
},
{
"name": "league/flysystem-aws-s3-v3",
"version": "3.16.0",
"source": {
"type": "git",
"url": "https://github.com/thephpleague/flysystem-aws-s3-v3.git",
"reference": "ded9ba346bb01cb9cc4cc7f2743c2c0e14d18e1c"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/thephpleague/flysystem-aws-s3-v3/zipball/ded9ba346bb01cb9cc4cc7f2743c2c0e14d18e1c",
"reference": "ded9ba346bb01cb9cc4cc7f2743c2c0e14d18e1c",
"shasum": ""
},
"require": {
"aws/aws-sdk-php": "^3.220.0",
"league/flysystem": "^3.10.0",
"league/mime-type-detection": "^1.0.0",
"php": "^8.0.2"
},
"conflict": {
"guzzlehttp/guzzle": "<7.0",
"guzzlehttp/ringphp": "<1.1.1"
},
"type": "library",
"autoload": {
"psr-4": {
"League\\Flysystem\\AwsS3V3\\": ""
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "Frank de Jonge",
"email": "info@frankdejonge.nl"
}
],
"description": "AWS S3 filesystem adapter for Flysystem.",
"keywords": [
"Flysystem",
"aws",
"file",
"files",
"filesystem",
"s3",
"storage"
],
"support": {
"issues": "https://github.com/thephpleague/flysystem-aws-s3-v3/issues",
"source": "https://github.com/thephpleague/flysystem-aws-s3-v3/tree/3.16.0"
},
"funding": [
{
"url": "https://ecologi.com/frankdejonge",
"type": "custom"
},
{
"url": "https://github.com/frankdejonge",
"type": "github"
}
],
"time": "2023-08-30T10:14:57+00:00"
}, },
{ {
"name": "league/flysystem-local", "name": "league/flysystem-local",
@ -1965,16 +2180,16 @@
}, },
{ {
"name": "league/mime-type-detection", "name": "league/mime-type-detection",
"version": "1.13.0", "version": "1.14.0",
"source": { "source": {
"type": "git", "type": "git",
"url": "https://github.com/thephpleague/mime-type-detection.git", "url": "https://github.com/thephpleague/mime-type-detection.git",
"reference": "a6dfb1194a2946fcdc1f38219445234f65b35c96" "reference": "b6a5854368533df0295c5761a0253656a2e52d9e"
}, },
"dist": { "dist": {
"type": "zip", "type": "zip",
"url": "https://api.github.com/repos/thephpleague/mime-type-detection/zipball/a6dfb1194a2946fcdc1f38219445234f65b35c96", "url": "https://api.github.com/repos/thephpleague/mime-type-detection/zipball/b6a5854368533df0295c5761a0253656a2e52d9e",
"reference": "a6dfb1194a2946fcdc1f38219445234f65b35c96", "reference": "b6a5854368533df0295c5761a0253656a2e52d9e",
"shasum": "" "shasum": ""
}, },
"require": { "require": {
@ -2005,7 +2220,7 @@
"description": "Mime-type detection for Flysystem", "description": "Mime-type detection for Flysystem",
"support": { "support": {
"issues": "https://github.com/thephpleague/mime-type-detection/issues", "issues": "https://github.com/thephpleague/mime-type-detection/issues",
"source": "https://github.com/thephpleague/mime-type-detection/tree/1.13.0" "source": "https://github.com/thephpleague/mime-type-detection/tree/1.14.0"
}, },
"funding": [ "funding": [
{ {
@ -2017,7 +2232,7 @@
"type": "tidelift" "type": "tidelift"
} }
], ],
"time": "2023-08-05T12:09:49+00:00" "time": "2023-10-17T14:13:20+00:00"
}, },
{ {
"name": "monolog/monolog", "name": "monolog/monolog",
@ -2120,6 +2335,72 @@
], ],
"time": "2023-06-21T08:46:11+00:00" "time": "2023-06-21T08:46:11+00:00"
}, },
{
"name": "mtdowling/jmespath.php",
"version": "2.7.0",
"source": {
"type": "git",
"url": "https://github.com/jmespath/jmespath.php.git",
"reference": "bbb69a935c2cbb0c03d7f481a238027430f6440b"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/jmespath/jmespath.php/zipball/bbb69a935c2cbb0c03d7f481a238027430f6440b",
"reference": "bbb69a935c2cbb0c03d7f481a238027430f6440b",
"shasum": ""
},
"require": {
"php": "^7.2.5 || ^8.0",
"symfony/polyfill-mbstring": "^1.17"
},
"require-dev": {
"composer/xdebug-handler": "^3.0.3",
"phpunit/phpunit": "^8.5.33"
},
"bin": [
"bin/jp.php"
],
"type": "library",
"extra": {
"branch-alias": {
"dev-master": "2.7-dev"
}
},
"autoload": {
"files": [
"src/JmesPath.php"
],
"psr-4": {
"JmesPath\\": "src/"
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "Graham Campbell",
"email": "hello@gjcampbell.co.uk",
"homepage": "https://github.com/GrahamCampbell"
},
{
"name": "Michael Dowling",
"email": "mtdowling@gmail.com",
"homepage": "https://github.com/mtdowling"
}
],
"description": "Declaratively specify how to extract elements from a JSON document",
"keywords": [
"json",
"jsonpath"
],
"support": {
"issues": "https://github.com/jmespath/jmespath.php/issues",
"source": "https://github.com/jmespath/jmespath.php/tree/2.7.0"
},
"time": "2023-08-25T10:54:48+00:00"
},
{ {
"name": "nesbot/carbon", "name": "nesbot/carbon",
"version": "2.71.0", "version": "2.71.0",
@ -2851,16 +3132,16 @@
}, },
{ {
"name": "psr/http-message", "name": "psr/http-message",
"version": "2.0", "version": "1.1",
"source": { "source": {
"type": "git", "type": "git",
"url": "https://github.com/php-fig/http-message.git", "url": "https://github.com/php-fig/http-message.git",
"reference": "402d35bcb92c70c026d1a6a9883f06b2ead23d71" "reference": "cb6ce4845ce34a8ad9e68117c10ee90a29919eba"
}, },
"dist": { "dist": {
"type": "zip", "type": "zip",
"url": "https://api.github.com/repos/php-fig/http-message/zipball/402d35bcb92c70c026d1a6a9883f06b2ead23d71", "url": "https://api.github.com/repos/php-fig/http-message/zipball/cb6ce4845ce34a8ad9e68117c10ee90a29919eba",
"reference": "402d35bcb92c70c026d1a6a9883f06b2ead23d71", "reference": "cb6ce4845ce34a8ad9e68117c10ee90a29919eba",
"shasum": "" "shasum": ""
}, },
"require": { "require": {
@ -2869,7 +3150,7 @@
"type": "library", "type": "library",
"extra": { "extra": {
"branch-alias": { "branch-alias": {
"dev-master": "2.0.x-dev" "dev-master": "1.1.x-dev"
} }
}, },
"autoload": { "autoload": {
@ -2884,7 +3165,7 @@
"authors": [ "authors": [
{ {
"name": "PHP-FIG", "name": "PHP-FIG",
"homepage": "https://www.php-fig.org/" "homepage": "http://www.php-fig.org/"
} }
], ],
"description": "Common interface for HTTP messages", "description": "Common interface for HTTP messages",
@ -2898,9 +3179,9 @@
"response" "response"
], ],
"support": { "support": {
"source": "https://github.com/php-fig/http-message/tree/2.0" "source": "https://github.com/php-fig/http-message/tree/1.1"
}, },
"time": "2023-04-04T09:54:51+00:00" "time": "2023-04-04T09:50:52+00:00"
}, },
{ {
"name": "psr/log", "name": "psr/log",

View file

@ -0,0 +1,30 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
return new class extends Migration
{
/**
* Run the migrations.
*/
public function up(): void
{
Schema::table('files', function (Blueprint $table) {
// adds 's3' boolean column to files table to signify if it was uploaded to s3
$table->boolean('s3')->default(0)->after('stored_at');
});
}
/**
* Reverse the migrations.
*/
public function down(): void
{
Schema::table('files', function (Blueprint $table) {
// reverse everything done above
$table->dropColumn('s3');
});
}
};

View file

@ -1,43 +1,43 @@
<script setup> <script setup>
import { computed, onMounted, onUnmounted, ref } from 'vue'; import { computed, onMounted, onUnmounted, ref } from "vue";
const props = defineProps({ const props = defineProps({
align: { align: {
type: String, type: String,
default: 'right', default: "right",
}, },
width: { width: {
type: String, type: String,
default: '48', default: "48",
}, },
contentClasses: { contentClasses: {
type: String, type: String,
default: 'py-1 bg-white dark:bg-gray-700', default: "py-1 bg-gray-700",
}, },
}); });
const closeOnEscape = (e) => { const closeOnEscape = (e) => {
if (open.value && e.key === 'Escape') { if (open.value && e.key === "Escape") {
open.value = false; open.value = false;
} }
}; };
onMounted(() => document.addEventListener('keydown', closeOnEscape)); onMounted(() => document.addEventListener("keydown", closeOnEscape));
onUnmounted(() => document.removeEventListener('keydown', closeOnEscape)); onUnmounted(() => document.removeEventListener("keydown", closeOnEscape));
const widthClass = computed(() => { const widthClass = computed(() => {
return { return {
48: 'w-48', 48: "w-48",
}[props.width.toString()]; }[props.width.toString()];
}); });
const alignmentClasses = computed(() => { const alignmentClasses = computed(() => {
if (props.align === 'left') { if (props.align === "left") {
return 'origin-top-left left-0'; return "origin-top-left left-0";
} else if (props.align === 'right') { } else if (props.align === "right") {
return 'origin-top-right right-0'; return "origin-top-right right-0";
} else { } else {
return 'origin-top'; return "origin-top";
} }
}); });
@ -51,7 +51,11 @@ const open = ref(false);
</div> </div>
<!-- Full Screen Dropdown Overlay --> <!-- Full Screen Dropdown Overlay -->
<div v-show="open" class="fixed inset-0 z-40" @click="open = false"></div> <div
v-show="open"
class="fixed inset-0 z-40"
@click="open = false"
></div>
<Transition <Transition
enter-active-class="transition ease-out duration-200" enter-active-class="transition ease-out duration-200"
@ -68,7 +72,10 @@ const open = ref(false);
style="display: none" style="display: none"
@click="open = false" @click="open = false"
> >
<div class="rounded-md ring-1 ring-black ring-opacity-5" :class="contentClasses"> <div
class="rounded-md ring-1 ring-black ring-opacity-5"
:class="contentClasses"
>
<slot name="content" /> <slot name="content" />
</div> </div>
</div> </div>

View file

@ -1,5 +1,5 @@
<script setup> <script setup>
import { Link } from '@inertiajs/vue3'; import { Link } from "@inertiajs/vue3";
defineProps({ defineProps({
href: { href: {
@ -12,7 +12,7 @@ defineProps({
<template> <template>
<Link <Link
:href="href" :href="href"
class="block w-full px-4 py-2 text-left text-sm leading-5 text-gray-700 dark:text-gray-300 hover:bg-gray-100 dark:hover:bg-gray-800 focus:outline-none focus:bg-gray-100 dark:focus:bg-gray-800 transition duration-150 ease-in-out" class="block w-full px-4 py-2 text-left text-sm leading-5 text-gray-300 hover:bg-gray-800 focus:outline-none focus:bg-gray-800 transition duration-150 ease-in-out"
> >
<slot /> <slot />
</Link> </Link>

View file

@ -8,7 +8,7 @@ defineProps({
<template> <template>
<div v-show="message"> <div v-show="message">
<p class="text-sm text-red-600 dark:text-red-400"> <p class="text-sm text-red-400">
{{ message }} {{ message }}
</p> </p>
</div> </div>

View file

@ -7,7 +7,7 @@ defineProps({
</script> </script>
<template> <template>
<label class="block font-medium text-gray-700 dark:text-gray-300"> <label class="block font-medium text-gray-300">
<span v-if="value">{{ value }}</span> <span v-if="value">{{ value }}</span>
<span v-else><slot /></span> <span v-else><slot /></span>
</label> </label>

View file

@ -14,8 +14,8 @@ const props = defineProps({
const classes = computed(() => const classes = computed(() =>
props.active props.active
? "flex items-center p-3 mb-1 bg-sky-600 rounded font-medium leading-5 text-slate-900 dark:text-gray-100 transition duration-150 ease-in-out hover:ring-sky-500" ? "flex items-center p-3 mb-1 bg-sky-600 rounded font-medium leading-5 text-gray-100 transition duration-150 ease-in-out hover:ring-sky-500"
: "flex items-center p-3 mb-1 rounded font-medium leading-5 text-gray-500 border border-zinc-900 dark:text-gray-400 hover:text-gray-700 dark:hover:text-zinc-100 hover:border-sky-500 hover:ring-sky-500 dark:hover:border-sky-600 hover:ring-sky-500 dark:hover:ring-sky-600 transition duration-150 ease-in-out" : "flex items-center p-3 mb-1 rounded font-medium leading-5 text-gray-500 border border-zinc-900 text-gray-400 hover:text-zinc-100 hover:ring-sky-500 hover:border-sky-600 hover:ring-sky-600 transition duration-150 ease-in-out"
); );
</script> </script>

View file

@ -14,7 +14,7 @@ const props = defineProps({
const classes = computed( const classes = computed(
() => () =>
"border border-zinc-900 block w-full pl-3 pr-4 py-2 text-left text-base font-medium text-gray-600 dark:text-zinc-100 hover:border-sky-500 dark:hover:border-sky-600 hover:ring-sky-500 dark:hover:ring-sky-600 transition duration-150 ease-in-out" "border border-zinc-900 block w-full pl-3 pr-4 py-2 text-left text-base font-medium text-zinc-100 hover:border-sky-600 hover:ring-sky-600 transition duration-150 ease-in-out"
); );
</script> </script>

View file

@ -23,7 +23,7 @@ defineExpose({ focus: () => input.value.focus() });
<template> <template>
<input <input
class="border-gray-300 dark:border-gray-700 dark:bg-zinc-900 dark:text-gray-300 focus:border-sky-500 dark:focus:border-sky-600 hover:border-sky-500 dark:hover:border-sky-600 rounded-md" class="border-gray-700 bg-zinc-900 text-gray-300 focus:border-sky-600 hover:border-sky-600 rounded-md"
:value="modelValue" :value="modelValue"
@input="$emit('update:modelValue', $event.target.value)" @input="$emit('update:modelValue', $event.target.value)"
ref="input" ref="input"

View file

@ -43,14 +43,14 @@ const plusClass = computed(() =>
leave-to-class="transform scale-95 opacity-0" leave-to-class="transform scale-95 opacity-0"
> >
<MenuItems <MenuItems
class="border border-gray-300 dark:border-gray-700 absolute left-0 mt-2 w-56 divide-y divide-gray-700 rounded-md bg-zinc-900 focus:outline-none z-50" class="border border-gray-700 absolute left-0 mt-2 w-56 divide-y divide-gray-700 rounded-md bg-zinc-900 focus:outline-none z-50"
> >
<div class="px-1 py-1"> <div class="px-1 py-1">
<MenuItem v-slot="{ active }" <MenuItem v-slot="{ active }"
><a ><a
href="#" href="#"
@click.prevent="toggleNewFolderModal(true)" @click.prevent="toggleNewFolderModal(true)"
class="block w-full items-center rounded-md py-2 border border-zinc-900 block w-full pl-3 pr-4 py-2 text-left text-base font-medium text-gray-600 dark:text-zinc-100 hover:border-sky-500 dark:hover:border-sky-600 hover:ring-sky-500 dark:hover:ring-sky-600 transition duration-150 ease-in-out" class="block w-full items-center rounded-md py-2 border border-zinc-900 block w-full pl-3 pr-4 py-2 text-left text-base font-medium text-zinc-100 hover:border-sky-600 hover:ring-sky-600 transition duration-150 ease-in-out"
>New Folder</a >New Folder</a
></MenuItem ></MenuItem
> >

View file

@ -12,7 +12,7 @@ const upload = (event) => {
<MenuItem v-slot="{ active }"> <MenuItem v-slot="{ active }">
<a <a
href="#" href="#"
class="block w-full items-center rounded-md py-2 border border-zinc-900 block w-full pl-3 pr-4 py-2 text-left text-base font-medium text-gray-600 dark:text-zinc-100 hover:border-sky-500 dark:hover:border-sky-600 hover:ring-sky-500 dark:hover:ring-sky-600transition duration-150 ease-in-out relative" class="block w-full items-center rounded-md py-2 border border-zinc-900 block w-full pl-3 pr-4 py-2 text-left text-base font-medium text-zinc-100 hover:border-sky-600 hover:ring-sky-600 transition duration-150 ease-in-out relative"
>Upload Files >Upload Files
<input <input
@change="upload" @change="upload"

View file

@ -12,7 +12,7 @@ const upload = (event) => {
<MenuItem v-slot="{ active }" <MenuItem v-slot="{ active }"
><a ><a
href="#" href="#"
class="block w-full items-center rounded-md py-2 border border-zinc-900 block w-full pl-3 pr-4 py-2 text-left text-base font-medium text-gray-600 dark:text-zinc-100 hover:border-sky-500 dark:hover:border-sky-600 hover:ring-sky-500 dark:hover:ring-sky-600transition duration-150 ease-in-out relative" class="block w-full items-center rounded-md py-2 border border-zinc-900 block w-full pl-3 pr-4 py-2 text-left text-base font-medium text-zinc-100 hover:border-sky-600 hover:ring-sky-600 transition duration-150 ease-in-out relative"
> >
Upload Folder Upload Folder
<input <input

View file

@ -24,12 +24,12 @@ import ResponsiveNavLink from "../ResponsiveNavLink.vue";
leave-to-class="transform scale-95 opacity-0" leave-to-class="transform scale-95 opacity-0"
> >
<MenuItems <MenuItems
class="border border-gray-300 dark:border-gray-700 absolute right-0 mt-2 w-56 origin-top-right divide-y divide-gray-100 rounded-md bg-zinc-900 focus:outline-none" class="border border-gray-700 absolute right-0 mt-2 w-56 origin-top-right divide-y divide-gray-100 rounded-md bg-zinc-900 focus:outline-none"
> >
<div class="px-1 py-1"> <div class="px-1 py-1">
<MenuItem v-slot="{ active }"> <MenuItem v-slot="{ active }">
<ResponsiveNavLink <ResponsiveNavLink
:href="route('profile')" :href="route('profile.edit')"
:class="[ :class="[
'group flex w-full items-center rounded-md py-2', 'group flex w-full items-center rounded-md py-2',
]" ]"

View file

@ -5,6 +5,18 @@
<main <main
class="flex flex-col flex-1 px-4 overflow-hidden items-center justify-center" class="flex flex-col flex-1 px-4 overflow-hidden items-center justify-center"
> >
<a
:href="route('login')"
method="get"
as="button"
type="button"
id="logotext"
class="text-5xl flex"
>
<span>DR</span>
<span class="text-3xl" id="lightning">&#11085;</span>
<span>VE</span>
</a>
<div <div
class="w-full sm:max-w-md mt-6 px-6 py-4 border-sky-600 border rounded overflow-hidden sm:rounded-lg" class="w-full sm:max-w-md mt-6 px-6 py-4 border-sky-600 border rounded overflow-hidden sm:rounded-lg"
> >

View file

@ -1,182 +0,0 @@
<div class="min-h-screen bg-gray-100 dark:bg-gray-900">
<nav
class="bg-white dark:bg-gray-800 border-b border-gray-100 dark:border-gray-700"
>
<!-- Primary Navigation Menu -->
<div class="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">
<div class="flex justify-between h-16">
<div class="flex">
<!-- Logo -->
<div class="shrink-0 flex items-center">
<Link :href="route('dashboard')">
<ApplicationLogo
class="block h-9 w-auto fill-current text-gray-800 dark:text-gray-200"
/>
</Link>
</div>
<!-- Navigation Links -->
<div
class="hidden space-x-8 sm:-my-px sm:ml-10 sm:flex"
>
<NavLink
:href="route('dashboard')"
:active="route().current('dashboard')"
>
Dashboard
</NavLink>
</div>
</div>
<div class="hidden sm:flex sm:items-center sm:ml-6">
<!-- Settings Dropdown -->
<div class="ml-3 relative">
<Dropdown align="right" width="48">
<template #trigger>
<span class="inline-flex rounded-md">
<button
type="button"
class="inline-flex items-center px-3 py-2 border border-transparent text-sm leading-4 font-medium rounded-md text-gray-500 dark:text-gray-400 bg-white dark:bg-gray-800 hover:text-gray-700 dark:hover:text-gray-300 focus:outline-none transition ease-in-out duration-150"
>
{{ $page.props.auth.user.name }}
<svg
class="ml-2 -mr-0.5 h-4 w-4"
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 20 20"
fill="currentColor"
>
<path
fill-rule="evenodd"
d="M5.293 7.293a1 1 0 011.414 0L10 10.586l3.293-3.293a1 1 0 111.414 1.414l-4 4a1 1 0 01-1.414 0l-4-4a1 1 0 010-1.414z"
clip-rule="evenodd"
/>
</svg>
</button>
</span>
</template>
<template #content>
<DropdownLink
:href="route('profile.edit')"
>
Profile
</DropdownLink>
<DropdownLink
:href="route('logout')"
method="post"
as="button"
>
Log Out
</DropdownLink>
</template>
</Dropdown>
</div>
</div>
<!-- Hamburger -->
<div class="-mr-2 flex items-center sm:hidden">
<button
@click="
showingNavigationDropdown =
!showingNavigationDropdown
"
class="inline-flex items-center justify-center p-2 rounded-md text-gray-400 dark:text-gray-500 hover:text-gray-500 dark:hover:text-gray-400 hover:bg-gray-100 dark:hover:bg-gray-900 focus:outline-none focus:bg-gray-100 dark:focus:bg-gray-900 focus:text-gray-500 dark:focus:text-gray-400 transition duration-150 ease-in-out"
>
<svg
class="h-6 w-6"
stroke="currentColor"
fill="none"
viewBox="0 0 24 24"
>
<path
:class="{
hidden: showingNavigationDropdown,
'inline-flex':
!showingNavigationDropdown,
}"
stroke-linecap="round"
stroke-linejoin="round"
stroke-width="2"
d="M4 6h16M4 12h16M4 18h16"
/>
<path
:class="{
hidden: !showingNavigationDropdown,
'inline-flex':
showingNavigationDropdown,
}"
stroke-linecap="round"
stroke-linejoin="round"
stroke-width="2"
d="M6 18L18 6M6 6l12 12"
/>
</svg>
</button>
</div>
</div>
</div>
<!-- Responsive Navigation Menu -->
<div
:class="{
block: showingNavigationDropdown,
hidden: !showingNavigationDropdown,
}"
class="sm:hidden"
>
<div class="pt-2 pb-3 space-y-1">
<ResponsiveNavLink
:href="route('dashboard')"
:active="route().current('dashboard')"
>
Dashboard
</ResponsiveNavLink>
</div>
<!-- Responsive Settings Options -->
<div
class="pt-4 pb-1 border-t border-gray-200 dark:border-gray-600"
>
<div class="px-4">
<div
class="font-medium text-base text-gray-800 dark:text-gray-200"
>
{{ $page.props.auth.user.name }}
</div>
<div class="font-medium text-sm text-gray-500">
{{ $page.props.auth.user.email }}
</div>
</div>
<div class="mt-3 space-y-1">
<ResponsiveNavLink :href="route('profile.edit')">
Profile
</ResponsiveNavLink>
<ResponsiveNavLink
:href="route('logout')"
method="post"
as="button"
>
Log Out
</ResponsiveNavLink>
</div>
</div>
</div>
</nav>
<!-- Page Heading -->
<header
class="bg-white dark:bg-gray-800 shadow"
v-if="$slots.header"
>
<div class="max-w-7xl mx-auto py-6 px-4 sm:px-6 lg:px-8">
<slot name="header" />
</div>
</header>
<!-- Page Content -->
<main>
<slot />
</main>
</div>

View file

@ -1,50 +0,0 @@
<script setup>
import GuestLayout from '@/Layouts/GuestLayout.vue';
import InputError from '@/Components/InputError.vue';
import InputLabel from '@/Components/InputLabel.vue';
import PrimaryButton from '@/Components/PrimaryButton.vue';
import TextInput from '@/Components/TextInput.vue';
import { Head, useForm } from '@inertiajs/vue3';
const form = useForm({
password: '',
});
const submit = () => {
form.post(route('password.confirm'), {
onFinish: () => form.reset(),
});
};
</script>
<template>
<GuestLayout>
<Head title="Confirm Password" />
<div class="mb-4 text-sm text-gray-600 dark:text-gray-400">
This is a secure area of the application. Please confirm your password before continuing.
</div>
<form @submit.prevent="submit">
<div>
<InputLabel for="password" value="Password" />
<TextInput
id="password"
type="password"
class="mt-1 block w-full"
v-model="form.password"
required
autocomplete="current-password"
autofocus
/>
<InputError class="mt-2" :message="form.errors.password" />
</div>
<div class="flex justify-end mt-4">
<PrimaryButton class="ml-4" :class="{ 'opacity-25': form.processing }" :disabled="form.processing">
Confirm
</PrimaryButton>
</div>
</form>
</GuestLayout>
</template>

View file

@ -1,10 +1,9 @@
<script setup> <script setup>
import GuestLayout from '@/Layouts/GuestLayout.vue'; import GuestLayout from "@/Layouts/GuestLayout.vue";
import InputError from '@/Components/InputError.vue'; import InputError from "@/Components/InputError.vue";
import InputLabel from '@/Components/InputLabel.vue'; import InputLabel from "@/Components/InputLabel.vue";
import PrimaryButton from '@/Components/PrimaryButton.vue'; import TextInput from "@/Components/TextInput.vue";
import TextInput from '@/Components/TextInput.vue'; import { Head, useForm } from "@inertiajs/vue3";
import { Head, useForm } from '@inertiajs/vue3';
defineProps({ defineProps({
status: { status: {
@ -13,11 +12,11 @@ defineProps({
}); });
const form = useForm({ const form = useForm({
email: '', email: "",
}); });
const submit = () => { const submit = () => {
form.post(route('password.email')); form.post(route("password.email"));
}; };
</script> </script>
@ -25,12 +24,13 @@ const submit = () => {
<GuestLayout> <GuestLayout>
<Head title="Forgot Password" /> <Head title="Forgot Password" />
<div class="mb-4 text-sm text-gray-600 dark:text-gray-400"> <div class="mb-4 text-sm text-gray-400">
Forgot your password? No problem. Just let us know your email address and we will email you a password reset Forgot your password? No problem. Just let us know your email
link that will allow you to choose a new one. address and we will email you a password reset link that will allow
you to choose a new one.
</div> </div>
<div v-if="status" class="mb-4 font-medium text-sm text-green-600 dark:text-green-400"> <div v-if="status" class="mb-4 font-medium text-sm text-green-400">
{{ status }} {{ status }}
</div> </div>
@ -52,9 +52,13 @@ const submit = () => {
</div> </div>
<div class="flex items-center justify-end mt-4"> <div class="flex items-center justify-end mt-4">
<PrimaryButton :class="{ 'opacity-25': form.processing }" :disabled="form.processing"> <button
class="px-6 py-3 border-sky-600 border rounded-lg hover:bg-sky-600"
:class="{ 'opacity-25': form.processing }"
:disabled="form.processing"
>
Email Password Reset Link Email Password Reset Link
</PrimaryButton> </button>
</div> </div>
</form> </form>
</GuestLayout> </GuestLayout>

View file

@ -25,7 +25,7 @@ const form = useForm({
const submit = () => { const submit = () => {
form.post(route("login"), { form.post(route("login"), {
onFinish: () => form.reset("password"), onFinish: () => form.reset("password"),
onSuccess: () => router.visit(route("/files")), onSuccess: () => router.visit(route("/verify-email")),
}); });
}; };
</script> </script>
@ -73,18 +73,23 @@ const submit = () => {
<div class="block mt-4"> <div class="block mt-4">
<label class="flex items-center"> <label class="flex items-center">
<Checkbox name="remember" v-model:checked="form.remember" /> <Checkbox name="remember" v-model:checked="form.remember" />
<span class="ml-2 text-sm text-gray-600 dark:text-gray-400" <span class="ml-2 text-sm text-gray-400">Remember me</span>
>Remember me</span
>
</label> </label>
</div> </div>
<div class="flex items-center justify-end mt-4 gap-6"> <div class="flex items-center justify-end mt-4 gap-6">
<Link <Link
:href="route('register')" :href="route('register')"
class="underline text-sm text-gray-600 dark:text-gray-400 hover:text-gray-900 dark:hover:text-gray-100 rounded-md" class="underline text-sm text-gray-400 hover:text-gray-100 rounded-md"
> >
Don't have an account? Register
</Link>
<Link
:href="route('password.request')"
class="underline text-sm text-gray-400 hover:text-gray-100 rounded-md"
>
Forgot your password?
</Link> </Link>
<button <button

View file

@ -16,9 +16,6 @@ const submit = () => {
form.post(route("register"), { form.post(route("register"), {
onFinish: () => form.reset("password", "password_confirmation"), onFinish: () => form.reset("password", "password_confirmation"),
onSuccess: () => { onSuccess: () => {
alert(
"Registration successful. Please check your e-mails for login link."
);
router.visit(route("/login")); router.visit(route("/login"));
}, },
}); });
@ -100,7 +97,7 @@ const submit = () => {
<div class="flex items-center justify-end mt-6 gap-6"> <div class="flex items-center justify-end mt-6 gap-6">
<Link <Link
:href="route('login')" :href="route('login')"
class="underline text-sm text-gray-600 dark:text-gray-400 hover:text-gray-900 dark:hover:text-gray-100 rounded-md" class="underline text-sm text-gray-400 hover:text-gray-100 rounded-md"
> >
Already registered? Already registered?
</Link> </Link>

View file

@ -1,8 +1,7 @@
<script setup> <script setup>
import { computed } from 'vue'; import { computed } from "vue";
import GuestLayout from '@/Layouts/GuestLayout.vue'; import GuestLayout from "@/Layouts/GuestLayout.vue";
import PrimaryButton from '@/Components/PrimaryButton.vue'; import { Head, Link, useForm } from "@inertiajs/vue3";
import { Head, Link, useForm } from '@inertiajs/vue3';
const props = defineProps({ const props = defineProps({
status: { status: {
@ -13,36 +12,47 @@ const props = defineProps({
const form = useForm({}); const form = useForm({});
const submit = () => { const submit = () => {
form.post(route('verification.send')); form.post(route("verification.send"));
}; };
const verificationLinkSent = computed(() => props.status === 'verification-link-sent'); const verificationLinkSent = computed(
() => props.status === "verification-link-sent"
);
</script> </script>
<template> <template>
<GuestLayout> <GuestLayout>
<Head title="Email Verification" /> <Head title="Email Verification" />
<div class="mb-4 text-sm text-gray-600 dark:text-gray-400"> <div class="mb-4 text-sm text-gray-400">
Thanks for signing up! Before getting started, could you verify your email address by clicking on the link Thanks for signing up! Before getting started, could you verify your
we just emailed to you? If you didn't receive the email, we will gladly send you another. email address by clicking on the link we just emailed to you? If you
didn't receive the email, we will gladly send you another.
</div> </div>
<div class="mb-4 font-medium text-sm text-green-600 dark:text-green-400" v-if="verificationLinkSent"> <div
A new verification link has been sent to the email address you provided during registration. class="mb-4 font-medium text-sm text-green-400"
v-if="verificationLinkSent"
>
A new verification link has been sent to the email address you
provided during registration.
</div> </div>
<form @submit.prevent="submit"> <form @submit.prevent="submit">
<div class="mt-4 flex items-center justify-between"> <div class="mt-4 flex items-center justify-between">
<PrimaryButton :class="{ 'opacity-25': form.processing }" :disabled="form.processing"> <button
class="px-6 py-3 border-sky-600 border rounded-lg hover:bg-sky-600"
:class="{ 'opacity-25': form.processing }"
:disabled="form.processing"
>
Resend Verification Email Resend Verification Email
</PrimaryButton> </button>
<Link <Link
:href="route('logout')" :href="route('logout')"
method="post" method="post"
as="button" as="button"
class="underline text-sm text-gray-600 dark:text-gray-400 hover:text-gray-900 dark:hover:text-gray-100 rounded-md focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-indigo-500 dark:focus:ring-offset-gray-800" class="px-6 py-3 border-red-600 border rounded-lg hover:bg-red-600"
>Log Out</Link >Log Out</Link
> >
</div> </div>

View file

@ -1,9 +1,9 @@
<script setup> <script setup>
import AuthenticatedLayout from '@/Layouts/AuthenticatedLayout.vue'; import AuthenticatedLayout from "@/Layouts/AuthenticatedLayout.vue";
import DeleteUserForm from './Partials/DeleteUserForm.vue'; import DeleteUserForm from "./Partials/DeleteUserForm.vue";
import UpdatePasswordForm from './Partials/UpdatePasswordForm.vue'; import UpdatePasswordForm from "./Partials/UpdatePasswordForm.vue";
import UpdateProfileInformationForm from './Partials/UpdateProfileInformationForm.vue'; import UpdateProfileInformationForm from "./Partials/UpdateProfileInformationForm.vue";
import { Head } from '@inertiajs/vue3'; import { Head } from "@inertiajs/vue3";
defineProps({ defineProps({
mustVerifyEmail: { mustVerifyEmail: {
@ -19,13 +19,9 @@ defineProps({
<Head title="Profile" /> <Head title="Profile" />
<AuthenticatedLayout> <AuthenticatedLayout>
<template #header> <div class="py-12 overflow-auto">
<h2 class="font-semibold text-xl text-gray-800 dark:text-gray-200 leading-tight">Profile</h2>
</template>
<div class="py-12">
<div class="max-w-7xl mx-auto sm:px-6 lg:px-8 space-y-6"> <div class="max-w-7xl mx-auto sm:px-6 lg:px-8 space-y-6">
<div class="p-4 sm:p-8 bg-white dark:bg-gray-800 shadow sm:rounded-lg"> <div class="p-4 sm:p-8 border border-sky-600 rounded-lg">
<UpdateProfileInformationForm <UpdateProfileInformationForm
:must-verify-email="mustVerifyEmail" :must-verify-email="mustVerifyEmail"
:status="status" :status="status"
@ -33,11 +29,11 @@ defineProps({
/> />
</div> </div>
<div class="p-4 sm:p-8 bg-white dark:bg-gray-800 shadow sm:rounded-lg"> <div class="p-4 sm:p-8 border border-sky-600 rounded-lg">
<UpdatePasswordForm class="max-w-xl" /> <UpdatePasswordForm class="max-w-xl" />
</div> </div>
<div class="p-4 sm:p-8 bg-white dark:bg-gray-800 shadow sm:rounded-lg"> <div class="p-4 sm:p-8 border border-sky-600 rounded-lg">
<DeleteUserForm class="max-w-xl" /> <DeleteUserForm class="max-w-xl" />
</div> </div>
</div> </div>

View file

@ -1,18 +1,16 @@
<script setup> <script setup>
import DangerButton from '@/Components/DangerButton.vue'; import InputError from "@/Components/InputError.vue";
import InputError from '@/Components/InputError.vue'; import InputLabel from "@/Components/InputLabel.vue";
import InputLabel from '@/Components/InputLabel.vue'; import Modal from "@/Components/Modal.vue";
import Modal from '@/Components/Modal.vue'; import TextInput from "@/Components/TextInput.vue";
import SecondaryButton from '@/Components/SecondaryButton.vue'; import { useForm } from "@inertiajs/vue3";
import TextInput from '@/Components/TextInput.vue'; import { nextTick, ref } from "vue";
import { useForm } from '@inertiajs/vue3';
import { nextTick, ref } from 'vue';
const confirmingUserDeletion = ref(false); const confirmingUserDeletion = ref(false);
const passwordInput = ref(null); const passwordInput = ref(null);
const form = useForm({ const form = useForm({
password: '', password: "",
}); });
const confirmUserDeletion = () => { const confirmUserDeletion = () => {
@ -22,7 +20,7 @@ const confirmUserDeletion = () => {
}; };
const deleteUser = () => { const deleteUser = () => {
form.delete(route('profile.destroy'), { form.delete(route("profile.destroy"), {
preserveScroll: true, preserveScroll: true,
onSuccess: () => closeModal(), onSuccess: () => closeModal(),
onError: () => passwordInput.value.focus(), onError: () => passwordInput.value.focus(),
@ -40,29 +38,40 @@ const closeModal = () => {
<template> <template>
<section class="space-y-6"> <section class="space-y-6">
<header> <header>
<h2 class="text-lg font-medium text-gray-900 dark:text-gray-100">Delete Account</h2> <h2 class="text-lg font-medium text-gray-100">Delete Account</h2>
<p class="mt-1 text-sm text-gray-600 dark:text-gray-400"> <p class="mt-1 text-sm text-gray-400">
Once your account is deleted, all of its resources and data will be permanently deleted. Before deleting Once your account is deleted, all of its resources and data will
your account, please download any data or information that you wish to retain. be permanently deleted. Before deleting your account, please
download any data or information that you wish to retain.
</p> </p>
</header> </header>
<DangerButton @click="confirmUserDeletion">Delete Account</DangerButton> <button
class="px-6 py-3 border-red-600 border rounded-lg hover:bg-red-600 hover:border-red-600"
@click="confirmUserDeletion"
>
Delete Account
</button>
<Modal :show="confirmingUserDeletion" @close="closeModal"> <Modal :show="confirmingUserDeletion" @close="closeModal">
<div class="p-6"> <div class="p-6">
<h2 class="text-lg font-medium text-gray-900 dark:text-gray-100"> <h2 class="text-lg font-medium text-gray-100">
Are you sure you want to delete your account? Are you sure you want to delete your account?
</h2> </h2>
<p class="mt-1 text-sm text-gray-600 dark:text-gray-400"> <p class="mt-1 text-sm text-gray-400">
Once your account is deleted, all of its resources and data will be permanently deleted. Please Once your account is deleted, all of its resources and data
enter your password to confirm you would like to permanently delete your account. will be permanently deleted. Please enter your password to
confirm you would like to permanently delete your account.
</p> </p>
<div class="mt-6"> <div class="mt-6">
<InputLabel for="password" value="Password" class="sr-only" /> <InputLabel
for="password"
value="Password"
class="sr-only"
/>
<TextInput <TextInput
id="password" id="password"
@ -78,16 +87,14 @@ const closeModal = () => {
</div> </div>
<div class="mt-6 flex justify-end"> <div class="mt-6 flex justify-end">
<SecondaryButton @click="closeModal"> Cancel </SecondaryButton> <button
class="px-6 py-3 border-red-600 border rounded-lg hover:bg-red-600 hover:border-red-600"
<DangerButton
class="ml-3"
:class="{ 'opacity-25': form.processing }" :class="{ 'opacity-25': form.processing }"
:disabled="form.processing" :disabled="form.processing"
@click="deleteUser" @click="deleteUser"
> >
Delete Account Delete Account
</DangerButton> </button>
</div> </div>
</div> </div>
</Modal> </Modal>

View file

@ -1,31 +1,30 @@
<script setup> <script setup>
import InputError from '@/Components/InputError.vue'; import InputError from "@/Components/InputError.vue";
import InputLabel from '@/Components/InputLabel.vue'; import InputLabel from "@/Components/InputLabel.vue";
import PrimaryButton from '@/Components/PrimaryButton.vue'; import TextInput from "@/Components/TextInput.vue";
import TextInput from '@/Components/TextInput.vue'; import { useForm } from "@inertiajs/vue3";
import { useForm } from '@inertiajs/vue3'; import { ref } from "vue";
import { ref } from 'vue';
const passwordInput = ref(null); const passwordInput = ref(null);
const currentPasswordInput = ref(null); const currentPasswordInput = ref(null);
const form = useForm({ const form = useForm({
current_password: '', current_password: "",
password: '', password: "",
password_confirmation: '', password_confirmation: "",
}); });
const updatePassword = () => { const updatePassword = () => {
form.put(route('password.update'), { form.put(route("password.update"), {
preserveScroll: true, preserveScroll: true,
onSuccess: () => form.reset(), onSuccess: () => form.reset(),
onError: () => { onError: () => {
if (form.errors.password) { if (form.errors.password) {
form.reset('password', 'password_confirmation'); form.reset("password", "password_confirmation");
passwordInput.value.focus(); passwordInput.value.focus();
} }
if (form.errors.current_password) { if (form.errors.current_password) {
form.reset('current_password'); form.reset("current_password");
currentPasswordInput.value.focus(); currentPasswordInput.value.focus();
} }
}, },
@ -36,10 +35,11 @@ const updatePassword = () => {
<template> <template>
<section> <section>
<header> <header>
<h2 class="text-lg font-medium text-gray-900 dark:text-gray-100">Update Password</h2> <h2 class="text-lg font-medium text-gray-100">Update Password</h2>
<p class="mt-1 text-sm text-gray-600 dark:text-gray-400"> <p class="mt-1 text-sm text-gray-400">
Ensure your account is using a long, random password to stay secure. Ensure your account is using a long, random password to stay
secure.
</p> </p>
</header> </header>
@ -56,7 +56,10 @@ const updatePassword = () => {
autocomplete="current-password" autocomplete="current-password"
/> />
<InputError :message="form.errors.current_password" class="mt-2" /> <InputError
:message="form.errors.current_password"
class="mt-2"
/>
</div> </div>
<div> <div>
@ -75,7 +78,10 @@ const updatePassword = () => {
</div> </div>
<div> <div>
<InputLabel for="password_confirmation" value="Confirm Password" /> <InputLabel
for="password_confirmation"
value="Confirm Password"
/>
<TextInput <TextInput
id="password_confirmation" id="password_confirmation"
@ -85,11 +91,19 @@ const updatePassword = () => {
autocomplete="new-password" autocomplete="new-password"
/> />
<InputError :message="form.errors.password_confirmation" class="mt-2" /> <InputError
:message="form.errors.password_confirmation"
class="mt-2"
/>
</div> </div>
<div class="flex items-center gap-4"> <div class="flex items-center gap-4">
<PrimaryButton :disabled="form.processing">Save</PrimaryButton> <button
class="px-6 py-3 border-sky-600 border rounded-lg hover:bg-sky-600"
:disabled="form.processing"
>
Save
</button>
<Transition <Transition
enter-active-class="transition ease-in-out" enter-active-class="transition ease-in-out"
@ -97,7 +111,12 @@ const updatePassword = () => {
leave-active-class="transition ease-in-out" leave-active-class="transition ease-in-out"
leave-to-class="opacity-0" leave-to-class="opacity-0"
> >
<p v-if="form.recentlySuccessful" class="text-sm text-gray-600 dark:text-gray-400">Saved.</p> <p
v-if="form.recentlySuccessful"
class="text-sm text-gray-400"
>
Saved.
</p>
</Transition> </Transition>
</div> </div>
</form> </form>

View file

@ -1,9 +1,8 @@
<script setup> <script setup>
import InputError from '@/Components/InputError.vue'; import InputError from "@/Components/InputError.vue";
import InputLabel from '@/Components/InputLabel.vue'; import InputLabel from "@/Components/InputLabel.vue";
import PrimaryButton from '@/Components/PrimaryButton.vue'; import TextInput from "@/Components/TextInput.vue";
import TextInput from '@/Components/TextInput.vue'; import { Link, useForm, usePage } from "@inertiajs/vue3";
import { Link, useForm, usePage } from '@inertiajs/vue3';
defineProps({ defineProps({
mustVerifyEmail: { mustVerifyEmail: {
@ -25,14 +24,19 @@ const form = useForm({
<template> <template>
<section> <section>
<header> <header>
<h2 class="text-lg font-medium text-gray-900 dark:text-gray-100">Profile Information</h2> <h2 class="text-lg font-medium text-gray-100">
Profile Information
</h2>
<p class="mt-1 text-sm text-gray-600 dark:text-gray-400"> <p class="mt-1 text-sm text-gray-400">
Update your account's profile information and email address. Update your account's profile information and email address.
</p> </p>
</header> </header>
<form @submit.prevent="form.patch(route('profile.update'))" class="mt-6 space-y-6"> <form
@submit.prevent="form.patch(route('profile.update'))"
class="mt-6 space-y-6"
>
<div> <div>
<InputLabel for="name" value="Name" /> <InputLabel for="name" value="Name" />
@ -65,13 +69,13 @@ const form = useForm({
</div> </div>
<div v-if="mustVerifyEmail && user.email_verified_at === null"> <div v-if="mustVerifyEmail && user.email_verified_at === null">
<p class="text-sm mt-2 text-gray-800 dark:text-gray-200"> <p class="text-sm mt-2 text-gray-200">
Your email address is unverified. Your email address is unverified.
<Link <Link
:href="route('verification.send')" :href="route('verification.send')"
method="post" method="post"
as="button" as="button"
class="underline text-sm text-gray-600 dark:text-gray-400 hover:text-gray-900 dark:hover:text-gray-100 rounded-md focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-indigo-500 dark:focus:ring-offset-gray-800" class="underline text-sm text-gray-400 hover:text-gray-100 rounded-md focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-offset-gray-800"
> >
Click here to re-send the verification email. Click here to re-send the verification email.
</Link> </Link>
@ -79,14 +83,19 @@ const form = useForm({
<div <div
v-show="status === 'verification-link-sent'" v-show="status === 'verification-link-sent'"
class="mt-2 font-medium text-sm text-green-600 dark:text-green-400" class="mt-2 font-medium text-sm text-green-400"
> >
A new verification link has been sent to your email address. A new verification link has been sent to your email address.
</div> </div>
</div> </div>
<div class="flex items-center gap-4"> <div class="flex items-center gap-4">
<PrimaryButton :disabled="form.processing">Save</PrimaryButton> <button
class="px-6 py-3 border-sky-600 border rounded-lg hover:bg-sky-600"
:disabled="form.processing"
>
Save
</button>
<Transition <Transition
enter-active-class="transition ease-in-out" enter-active-class="transition ease-in-out"
@ -94,7 +103,12 @@ const form = useForm({
leave-active-class="transition ease-in-out" leave-active-class="transition ease-in-out"
leave-to-class="opacity-0" leave-to-class="opacity-0"
> >
<p v-if="form.recentlySuccessful" class="text-sm text-gray-600 dark:text-gray-400">Saved.</p> <p
v-if="form.recentlySuccessful"
class="text-sm text-gray-400"
>
Saved.
</p>
</Transition> </Transition>
</div> </div>
</form> </form>