removed old boilerplate, implemented S3 uploading.
This commit is contained in:
parent
d40584de14
commit
c2ec2af33c
29 changed files with 615 additions and 395 deletions
|
|
@ -1,3 +1,4 @@
|
|||
# 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.
|
||||
Uses S3 for storing and retrieving files.
|
||||
|
|
|
|||
|
|
@ -12,6 +12,8 @@ use App\Http\Requests\SharedFiles;
|
|||
|
||||
use App\Http\Resources\FileResource;
|
||||
|
||||
use App\Jobs\UploadToS3;
|
||||
|
||||
use Illuminate\Http\Request;
|
||||
|
||||
use Illuminate\Support\Facades\Auth;
|
||||
|
|
@ -827,12 +829,16 @@ class FileController extends Controller
|
|||
// loop over files, make and upload each one
|
||||
foreach ($files as $userFile) {
|
||||
$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->name = $userFile->getClientOriginalName();
|
||||
$file->mimetype = $userFile->getMimeType();
|
||||
$file->size = $userFile->getSize();
|
||||
$file->s3 = 0; // file has NOT been uploaded to s3
|
||||
$parent->appendNode($file);
|
||||
|
||||
// upload file to S3
|
||||
UploadToS3::dispatch($file);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
61
app/Jobs/UploadToS3.php
Normal file
61
app/Jobs/UploadToS3.php
Normal 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
|
||||
}
|
||||
}
|
||||
|
|
@ -88,7 +88,7 @@ class File extends Model
|
|||
else $model->path = '';
|
||||
|
||||
// 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
|
||||
|
|
|
|||
|
|
@ -16,6 +16,8 @@
|
|||
"laravel/framework": "^10.10",
|
||||
"laravel/sanctum": "^3.2",
|
||||
"laravel/tinker": "^2.8",
|
||||
"league/flysystem-aws-s3-v3": "^3.0",
|
||||
"psr/http-message": "^1.0",
|
||||
"tightenco/ziggy": "^1.0"
|
||||
},
|
||||
"require-dev": {
|
||||
|
|
|
|||
327
composer.lock
generated
327
composer.lock
generated
|
|
@ -4,8 +4,157 @@
|
|||
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
|
||||
"This file is @generated automatically"
|
||||
],
|
||||
"content-hash": "6befa31489c527cc8174c05d5165886a",
|
||||
"content-hash": "edbfbe62b4cb8f6882359a6168f6d6e6",
|
||||
"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",
|
||||
"version": "0.11.0",
|
||||
|
|
@ -1815,16 +1964,16 @@
|
|||
},
|
||||
{
|
||||
"name": "league/flysystem",
|
||||
"version": "3.16.0",
|
||||
"version": "3.17.0",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/thephpleague/flysystem.git",
|
||||
"reference": "4fdf372ca6b63c6e281b1c01a624349ccb757729"
|
||||
"reference": "bd4c9b26849d82364119c68429541f1631fba94b"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/thephpleague/flysystem/zipball/4fdf372ca6b63c6e281b1c01a624349ccb757729",
|
||||
"reference": "4fdf372ca6b63c6e281b1c01a624349ccb757729",
|
||||
"url": "https://api.github.com/repos/thephpleague/flysystem/zipball/bd4c9b26849d82364119c68429541f1631fba94b",
|
||||
"reference": "bd4c9b26849d82364119c68429541f1631fba94b",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
|
|
@ -1842,8 +1991,8 @@
|
|||
"symfony/http-client": "<5.2"
|
||||
},
|
||||
"require-dev": {
|
||||
"async-aws/s3": "^1.5",
|
||||
"async-aws/simple-s3": "^1.1",
|
||||
"async-aws/s3": "^1.5 || ^2.0",
|
||||
"async-aws/simple-s3": "^1.1 || ^2.0",
|
||||
"aws/aws-sdk-php": "^3.220.0",
|
||||
"composer/semver": "^3.0",
|
||||
"ext-fileinfo": "*",
|
||||
|
|
@ -1889,7 +2038,7 @@
|
|||
],
|
||||
"support": {
|
||||
"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": [
|
||||
{
|
||||
|
|
@ -1901,7 +2050,73 @@
|
|||
"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",
|
||||
|
|
@ -1965,16 +2180,16 @@
|
|||
},
|
||||
{
|
||||
"name": "league/mime-type-detection",
|
||||
"version": "1.13.0",
|
||||
"version": "1.14.0",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/thephpleague/mime-type-detection.git",
|
||||
"reference": "a6dfb1194a2946fcdc1f38219445234f65b35c96"
|
||||
"reference": "b6a5854368533df0295c5761a0253656a2e52d9e"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/thephpleague/mime-type-detection/zipball/a6dfb1194a2946fcdc1f38219445234f65b35c96",
|
||||
"reference": "a6dfb1194a2946fcdc1f38219445234f65b35c96",
|
||||
"url": "https://api.github.com/repos/thephpleague/mime-type-detection/zipball/b6a5854368533df0295c5761a0253656a2e52d9e",
|
||||
"reference": "b6a5854368533df0295c5761a0253656a2e52d9e",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
|
|
@ -2005,7 +2220,7 @@
|
|||
"description": "Mime-type detection for Flysystem",
|
||||
"support": {
|
||||
"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": [
|
||||
{
|
||||
|
|
@ -2017,7 +2232,7 @@
|
|||
"type": "tidelift"
|
||||
}
|
||||
],
|
||||
"time": "2023-08-05T12:09:49+00:00"
|
||||
"time": "2023-10-17T14:13:20+00:00"
|
||||
},
|
||||
{
|
||||
"name": "monolog/monolog",
|
||||
|
|
@ -2120,6 +2335,72 @@
|
|||
],
|
||||
"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",
|
||||
"version": "2.71.0",
|
||||
|
|
@ -2851,16 +3132,16 @@
|
|||
},
|
||||
{
|
||||
"name": "psr/http-message",
|
||||
"version": "2.0",
|
||||
"version": "1.1",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/php-fig/http-message.git",
|
||||
"reference": "402d35bcb92c70c026d1a6a9883f06b2ead23d71"
|
||||
"reference": "cb6ce4845ce34a8ad9e68117c10ee90a29919eba"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/php-fig/http-message/zipball/402d35bcb92c70c026d1a6a9883f06b2ead23d71",
|
||||
"reference": "402d35bcb92c70c026d1a6a9883f06b2ead23d71",
|
||||
"url": "https://api.github.com/repos/php-fig/http-message/zipball/cb6ce4845ce34a8ad9e68117c10ee90a29919eba",
|
||||
"reference": "cb6ce4845ce34a8ad9e68117c10ee90a29919eba",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
|
|
@ -2869,7 +3150,7 @@
|
|||
"type": "library",
|
||||
"extra": {
|
||||
"branch-alias": {
|
||||
"dev-master": "2.0.x-dev"
|
||||
"dev-master": "1.1.x-dev"
|
||||
}
|
||||
},
|
||||
"autoload": {
|
||||
|
|
@ -2884,7 +3165,7 @@
|
|||
"authors": [
|
||||
{
|
||||
"name": "PHP-FIG",
|
||||
"homepage": "https://www.php-fig.org/"
|
||||
"homepage": "http://www.php-fig.org/"
|
||||
}
|
||||
],
|
||||
"description": "Common interface for HTTP messages",
|
||||
|
|
@ -2898,9 +3179,9 @@
|
|||
"response"
|
||||
],
|
||||
"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",
|
||||
|
|
|
|||
|
|
@ -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');
|
||||
});
|
||||
}
|
||||
};
|
||||
|
|
@ -1,43 +1,43 @@
|
|||
<script setup>
|
||||
import { computed, onMounted, onUnmounted, ref } from 'vue';
|
||||
import { computed, onMounted, onUnmounted, ref } from "vue";
|
||||
|
||||
const props = defineProps({
|
||||
align: {
|
||||
type: String,
|
||||
default: 'right',
|
||||
default: "right",
|
||||
},
|
||||
width: {
|
||||
type: String,
|
||||
default: '48',
|
||||
default: "48",
|
||||
},
|
||||
contentClasses: {
|
||||
type: String,
|
||||
default: 'py-1 bg-white dark:bg-gray-700',
|
||||
default: "py-1 bg-gray-700",
|
||||
},
|
||||
});
|
||||
|
||||
const closeOnEscape = (e) => {
|
||||
if (open.value && e.key === 'Escape') {
|
||||
if (open.value && e.key === "Escape") {
|
||||
open.value = false;
|
||||
}
|
||||
};
|
||||
|
||||
onMounted(() => document.addEventListener('keydown', closeOnEscape));
|
||||
onUnmounted(() => document.removeEventListener('keydown', closeOnEscape));
|
||||
onMounted(() => document.addEventListener("keydown", closeOnEscape));
|
||||
onUnmounted(() => document.removeEventListener("keydown", closeOnEscape));
|
||||
|
||||
const widthClass = computed(() => {
|
||||
return {
|
||||
48: 'w-48',
|
||||
48: "w-48",
|
||||
}[props.width.toString()];
|
||||
});
|
||||
|
||||
const alignmentClasses = computed(() => {
|
||||
if (props.align === 'left') {
|
||||
return 'origin-top-left left-0';
|
||||
} else if (props.align === 'right') {
|
||||
return 'origin-top-right right-0';
|
||||
if (props.align === "left") {
|
||||
return "origin-top-left left-0";
|
||||
} else if (props.align === "right") {
|
||||
return "origin-top-right right-0";
|
||||
} else {
|
||||
return 'origin-top';
|
||||
return "origin-top";
|
||||
}
|
||||
});
|
||||
|
||||
|
|
@ -51,7 +51,11 @@ const open = ref(false);
|
|||
</div>
|
||||
|
||||
<!-- 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
|
||||
enter-active-class="transition ease-out duration-200"
|
||||
|
|
@ -68,7 +72,10 @@ const open = ref(false);
|
|||
style="display: none"
|
||||
@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" />
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
<script setup>
|
||||
import { Link } from '@inertiajs/vue3';
|
||||
import { Link } from "@inertiajs/vue3";
|
||||
|
||||
defineProps({
|
||||
href: {
|
||||
|
|
@ -12,7 +12,7 @@ defineProps({
|
|||
<template>
|
||||
<Link
|
||||
: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 />
|
||||
</Link>
|
||||
|
|
|
|||
|
|
@ -8,7 +8,7 @@ defineProps({
|
|||
|
||||
<template>
|
||||
<div v-show="message">
|
||||
<p class="text-sm text-red-600 dark:text-red-400">
|
||||
<p class="text-sm text-red-400">
|
||||
{{ message }}
|
||||
</p>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -7,7 +7,7 @@ defineProps({
|
|||
</script>
|
||||
|
||||
<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-else><slot /></span>
|
||||
</label>
|
||||
|
|
|
|||
|
|
@ -14,8 +14,8 @@ const props = defineProps({
|
|||
|
||||
const classes = computed(() =>
|
||||
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 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 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 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>
|
||||
|
||||
|
|
|
|||
|
|
@ -14,7 +14,7 @@ const props = defineProps({
|
|||
|
||||
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>
|
||||
|
||||
|
|
|
|||
|
|
@ -23,7 +23,7 @@ defineExpose({ focus: () => input.value.focus() });
|
|||
|
||||
<template>
|
||||
<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"
|
||||
@input="$emit('update:modelValue', $event.target.value)"
|
||||
ref="input"
|
||||
|
|
|
|||
|
|
@ -43,14 +43,14 @@ const plusClass = computed(() =>
|
|||
leave-to-class="transform scale-95 opacity-0"
|
||||
>
|
||||
<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">
|
||||
<MenuItem v-slot="{ active }"
|
||||
><a
|
||||
href="#"
|
||||
@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
|
||||
></MenuItem
|
||||
>
|
||||
|
|
|
|||
|
|
@ -12,7 +12,7 @@ const upload = (event) => {
|
|||
<MenuItem v-slot="{ active }">
|
||||
<a
|
||||
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
|
||||
<input
|
||||
@change="upload"
|
||||
|
|
|
|||
|
|
@ -12,7 +12,7 @@ const upload = (event) => {
|
|||
<MenuItem v-slot="{ active }"
|
||||
><a
|
||||
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
|
||||
<input
|
||||
|
|
|
|||
|
|
@ -24,12 +24,12 @@ import ResponsiveNavLink from "../ResponsiveNavLink.vue";
|
|||
leave-to-class="transform scale-95 opacity-0"
|
||||
>
|
||||
<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">
|
||||
<MenuItem v-slot="{ active }">
|
||||
<ResponsiveNavLink
|
||||
:href="route('profile')"
|
||||
:href="route('profile.edit')"
|
||||
:class="[
|
||||
'group flex w-full items-center rounded-md py-2',
|
||||
]"
|
||||
|
|
|
|||
|
|
@ -5,6 +5,18 @@
|
|||
<main
|
||||
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">⭍</span>
|
||||
<span>VE</span>
|
||||
</a>
|
||||
<div
|
||||
class="w-full sm:max-w-md mt-6 px-6 py-4 border-sky-600 border rounded overflow-hidden sm:rounded-lg"
|
||||
>
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
|
@ -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>
|
||||
|
|
@ -1,10 +1,9 @@
|
|||
<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';
|
||||
import GuestLayout from "@/Layouts/GuestLayout.vue";
|
||||
import InputError from "@/Components/InputError.vue";
|
||||
import InputLabel from "@/Components/InputLabel.vue";
|
||||
import TextInput from "@/Components/TextInput.vue";
|
||||
import { Head, useForm } from "@inertiajs/vue3";
|
||||
|
||||
defineProps({
|
||||
status: {
|
||||
|
|
@ -13,11 +12,11 @@ defineProps({
|
|||
});
|
||||
|
||||
const form = useForm({
|
||||
email: '',
|
||||
email: "",
|
||||
});
|
||||
|
||||
const submit = () => {
|
||||
form.post(route('password.email'));
|
||||
form.post(route("password.email"));
|
||||
};
|
||||
</script>
|
||||
|
||||
|
|
@ -25,12 +24,13 @@ const submit = () => {
|
|||
<GuestLayout>
|
||||
<Head title="Forgot Password" />
|
||||
|
||||
<div class="mb-4 text-sm text-gray-600 dark:text-gray-400">
|
||||
Forgot your password? No problem. Just let us know your email address and we will email you a password reset
|
||||
link that will allow you to choose a new one.
|
||||
<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 link that will allow
|
||||
you to choose a new one.
|
||||
</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 }}
|
||||
</div>
|
||||
|
||||
|
|
@ -52,9 +52,13 @@ const submit = () => {
|
|||
</div>
|
||||
|
||||
<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
|
||||
</PrimaryButton>
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
</GuestLayout>
|
||||
|
|
|
|||
|
|
@ -25,7 +25,7 @@ const form = useForm({
|
|||
const submit = () => {
|
||||
form.post(route("login"), {
|
||||
onFinish: () => form.reset("password"),
|
||||
onSuccess: () => router.visit(route("/files")),
|
||||
onSuccess: () => router.visit(route("/verify-email")),
|
||||
});
|
||||
};
|
||||
</script>
|
||||
|
|
@ -73,18 +73,23 @@ const submit = () => {
|
|||
<div class="block mt-4">
|
||||
<label class="flex items-center">
|
||||
<Checkbox name="remember" v-model:checked="form.remember" />
|
||||
<span class="ml-2 text-sm text-gray-600 dark:text-gray-400"
|
||||
>Remember me</span
|
||||
>
|
||||
<span class="ml-2 text-sm text-gray-400">Remember me</span>
|
||||
</label>
|
||||
</div>
|
||||
|
||||
<div class="flex items-center justify-end mt-4 gap-6">
|
||||
<Link
|
||||
: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>
|
||||
|
||||
<button
|
||||
|
|
|
|||
|
|
@ -16,9 +16,6 @@ const submit = () => {
|
|||
form.post(route("register"), {
|
||||
onFinish: () => form.reset("password", "password_confirmation"),
|
||||
onSuccess: () => {
|
||||
alert(
|
||||
"Registration successful. Please check your e-mails for login link."
|
||||
);
|
||||
router.visit(route("/login"));
|
||||
},
|
||||
});
|
||||
|
|
@ -100,7 +97,7 @@ const submit = () => {
|
|||
<div class="flex items-center justify-end mt-6 gap-6">
|
||||
<Link
|
||||
: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?
|
||||
</Link>
|
||||
|
|
|
|||
|
|
@ -1,8 +1,7 @@
|
|||
<script setup>
|
||||
import { computed } from 'vue';
|
||||
import GuestLayout from '@/Layouts/GuestLayout.vue';
|
||||
import PrimaryButton from '@/Components/PrimaryButton.vue';
|
||||
import { Head, Link, useForm } from '@inertiajs/vue3';
|
||||
import { computed } from "vue";
|
||||
import GuestLayout from "@/Layouts/GuestLayout.vue";
|
||||
import { Head, Link, useForm } from "@inertiajs/vue3";
|
||||
|
||||
const props = defineProps({
|
||||
status: {
|
||||
|
|
@ -13,36 +12,47 @@ const props = defineProps({
|
|||
const form = useForm({});
|
||||
|
||||
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>
|
||||
|
||||
<template>
|
||||
<GuestLayout>
|
||||
<Head title="Email Verification" />
|
||||
|
||||
<div class="mb-4 text-sm text-gray-600 dark:text-gray-400">
|
||||
Thanks for signing up! Before getting started, could you verify your 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 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 we just emailed to you? If you
|
||||
didn't receive the email, we will gladly send you another.
|
||||
</div>
|
||||
|
||||
<div class="mb-4 font-medium text-sm text-green-600 dark:text-green-400" v-if="verificationLinkSent">
|
||||
A new verification link has been sent to the email address you provided during registration.
|
||||
<div
|
||||
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>
|
||||
|
||||
<form @submit.prevent="submit">
|
||||
<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
|
||||
</PrimaryButton>
|
||||
</button>
|
||||
|
||||
<Link
|
||||
:href="route('logout')"
|
||||
method="post"
|
||||
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
|
||||
>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -1,9 +1,9 @@
|
|||
<script setup>
|
||||
import AuthenticatedLayout from '@/Layouts/AuthenticatedLayout.vue';
|
||||
import DeleteUserForm from './Partials/DeleteUserForm.vue';
|
||||
import UpdatePasswordForm from './Partials/UpdatePasswordForm.vue';
|
||||
import UpdateProfileInformationForm from './Partials/UpdateProfileInformationForm.vue';
|
||||
import { Head } from '@inertiajs/vue3';
|
||||
import AuthenticatedLayout from "@/Layouts/AuthenticatedLayout.vue";
|
||||
import DeleteUserForm from "./Partials/DeleteUserForm.vue";
|
||||
import UpdatePasswordForm from "./Partials/UpdatePasswordForm.vue";
|
||||
import UpdateProfileInformationForm from "./Partials/UpdateProfileInformationForm.vue";
|
||||
import { Head } from "@inertiajs/vue3";
|
||||
|
||||
defineProps({
|
||||
mustVerifyEmail: {
|
||||
|
|
@ -19,13 +19,9 @@ defineProps({
|
|||
<Head title="Profile" />
|
||||
|
||||
<AuthenticatedLayout>
|
||||
<template #header>
|
||||
<h2 class="font-semibold text-xl text-gray-800 dark:text-gray-200 leading-tight">Profile</h2>
|
||||
</template>
|
||||
|
||||
<div class="py-12">
|
||||
<div class="py-12 overflow-auto">
|
||||
<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
|
||||
:must-verify-email="mustVerifyEmail"
|
||||
:status="status"
|
||||
|
|
@ -33,11 +29,11 @@ defineProps({
|
|||
/>
|
||||
</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" />
|
||||
</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" />
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -1,18 +1,16 @@
|
|||
<script setup>
|
||||
import DangerButton from '@/Components/DangerButton.vue';
|
||||
import InputError from '@/Components/InputError.vue';
|
||||
import InputLabel from '@/Components/InputLabel.vue';
|
||||
import Modal from '@/Components/Modal.vue';
|
||||
import SecondaryButton from '@/Components/SecondaryButton.vue';
|
||||
import TextInput from '@/Components/TextInput.vue';
|
||||
import { useForm } from '@inertiajs/vue3';
|
||||
import { nextTick, ref } from 'vue';
|
||||
import InputError from "@/Components/InputError.vue";
|
||||
import InputLabel from "@/Components/InputLabel.vue";
|
||||
import Modal from "@/Components/Modal.vue";
|
||||
import TextInput from "@/Components/TextInput.vue";
|
||||
import { useForm } from "@inertiajs/vue3";
|
||||
import { nextTick, ref } from "vue";
|
||||
|
||||
const confirmingUserDeletion = ref(false);
|
||||
const passwordInput = ref(null);
|
||||
|
||||
const form = useForm({
|
||||
password: '',
|
||||
password: "",
|
||||
});
|
||||
|
||||
const confirmUserDeletion = () => {
|
||||
|
|
@ -22,7 +20,7 @@ const confirmUserDeletion = () => {
|
|||
};
|
||||
|
||||
const deleteUser = () => {
|
||||
form.delete(route('profile.destroy'), {
|
||||
form.delete(route("profile.destroy"), {
|
||||
preserveScroll: true,
|
||||
onSuccess: () => closeModal(),
|
||||
onError: () => passwordInput.value.focus(),
|
||||
|
|
@ -40,29 +38,40 @@ const closeModal = () => {
|
|||
<template>
|
||||
<section class="space-y-6">
|
||||
<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">
|
||||
Once your account is deleted, all of its resources and data will be permanently deleted. Before deleting
|
||||
your account, please download any data or information that you wish to retain.
|
||||
<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 your account, please
|
||||
download any data or information that you wish to retain.
|
||||
</p>
|
||||
</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">
|
||||
<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?
|
||||
</h2>
|
||||
|
||||
<p class="mt-1 text-sm text-gray-600 dark:text-gray-400">
|
||||
Once your account is deleted, all of its resources and data will be permanently deleted. Please
|
||||
enter your password to confirm you would like to permanently delete your account.
|
||||
<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 enter your password to
|
||||
confirm you would like to permanently delete your account.
|
||||
</p>
|
||||
|
||||
<div class="mt-6">
|
||||
<InputLabel for="password" value="Password" class="sr-only" />
|
||||
<InputLabel
|
||||
for="password"
|
||||
value="Password"
|
||||
class="sr-only"
|
||||
/>
|
||||
|
||||
<TextInput
|
||||
id="password"
|
||||
|
|
@ -78,16 +87,14 @@ const closeModal = () => {
|
|||
</div>
|
||||
|
||||
<div class="mt-6 flex justify-end">
|
||||
<SecondaryButton @click="closeModal"> Cancel </SecondaryButton>
|
||||
|
||||
<DangerButton
|
||||
class="ml-3"
|
||||
<button
|
||||
class="px-6 py-3 border-red-600 border rounded-lg hover:bg-red-600 hover:border-red-600"
|
||||
:class="{ 'opacity-25': form.processing }"
|
||||
:disabled="form.processing"
|
||||
@click="deleteUser"
|
||||
>
|
||||
Delete Account
|
||||
</DangerButton>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</Modal>
|
||||
|
|
|
|||
|
|
@ -1,31 +1,30 @@
|
|||
<script setup>
|
||||
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 { useForm } from '@inertiajs/vue3';
|
||||
import { ref } from 'vue';
|
||||
import InputError from "@/Components/InputError.vue";
|
||||
import InputLabel from "@/Components/InputLabel.vue";
|
||||
import TextInput from "@/Components/TextInput.vue";
|
||||
import { useForm } from "@inertiajs/vue3";
|
||||
import { ref } from "vue";
|
||||
|
||||
const passwordInput = ref(null);
|
||||
const currentPasswordInput = ref(null);
|
||||
|
||||
const form = useForm({
|
||||
current_password: '',
|
||||
password: '',
|
||||
password_confirmation: '',
|
||||
current_password: "",
|
||||
password: "",
|
||||
password_confirmation: "",
|
||||
});
|
||||
|
||||
const updatePassword = () => {
|
||||
form.put(route('password.update'), {
|
||||
form.put(route("password.update"), {
|
||||
preserveScroll: true,
|
||||
onSuccess: () => form.reset(),
|
||||
onError: () => {
|
||||
if (form.errors.password) {
|
||||
form.reset('password', 'password_confirmation');
|
||||
form.reset("password", "password_confirmation");
|
||||
passwordInput.value.focus();
|
||||
}
|
||||
if (form.errors.current_password) {
|
||||
form.reset('current_password');
|
||||
form.reset("current_password");
|
||||
currentPasswordInput.value.focus();
|
||||
}
|
||||
},
|
||||
|
|
@ -36,10 +35,11 @@ const updatePassword = () => {
|
|||
<template>
|
||||
<section>
|
||||
<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">
|
||||
Ensure your account is using a long, random password to stay secure.
|
||||
<p class="mt-1 text-sm text-gray-400">
|
||||
Ensure your account is using a long, random password to stay
|
||||
secure.
|
||||
</p>
|
||||
</header>
|
||||
|
||||
|
|
@ -56,7 +56,10 @@ const updatePassword = () => {
|
|||
autocomplete="current-password"
|
||||
/>
|
||||
|
||||
<InputError :message="form.errors.current_password" class="mt-2" />
|
||||
<InputError
|
||||
:message="form.errors.current_password"
|
||||
class="mt-2"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
|
|
@ -75,7 +78,10 @@ const updatePassword = () => {
|
|||
</div>
|
||||
|
||||
<div>
|
||||
<InputLabel for="password_confirmation" value="Confirm Password" />
|
||||
<InputLabel
|
||||
for="password_confirmation"
|
||||
value="Confirm Password"
|
||||
/>
|
||||
|
||||
<TextInput
|
||||
id="password_confirmation"
|
||||
|
|
@ -85,11 +91,19 @@ const updatePassword = () => {
|
|||
autocomplete="new-password"
|
||||
/>
|
||||
|
||||
<InputError :message="form.errors.password_confirmation" class="mt-2" />
|
||||
<InputError
|
||||
:message="form.errors.password_confirmation"
|
||||
class="mt-2"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<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
|
||||
enter-active-class="transition ease-in-out"
|
||||
|
|
@ -97,7 +111,12 @@ const updatePassword = () => {
|
|||
leave-active-class="transition ease-in-out"
|
||||
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>
|
||||
</div>
|
||||
</form>
|
||||
|
|
|
|||
|
|
@ -1,9 +1,8 @@
|
|||
<script setup>
|
||||
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 { Link, useForm, usePage } from '@inertiajs/vue3';
|
||||
import InputError from "@/Components/InputError.vue";
|
||||
import InputLabel from "@/Components/InputLabel.vue";
|
||||
import TextInput from "@/Components/TextInput.vue";
|
||||
import { Link, useForm, usePage } from "@inertiajs/vue3";
|
||||
|
||||
defineProps({
|
||||
mustVerifyEmail: {
|
||||
|
|
@ -25,14 +24,19 @@ const form = useForm({
|
|||
<template>
|
||||
<section>
|
||||
<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.
|
||||
</p>
|
||||
</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>
|
||||
<InputLabel for="name" value="Name" />
|
||||
|
||||
|
|
@ -65,13 +69,13 @@ const form = useForm({
|
|||
</div>
|
||||
|
||||
<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.
|
||||
<Link
|
||||
:href="route('verification.send')"
|
||||
method="post"
|
||||
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.
|
||||
</Link>
|
||||
|
|
@ -79,14 +83,19 @@ const form = useForm({
|
|||
|
||||
<div
|
||||
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.
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<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
|
||||
enter-active-class="transition ease-in-out"
|
||||
|
|
@ -94,7 +103,12 @@ const form = useForm({
|
|||
leave-active-class="transition ease-in-out"
|
||||
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>
|
||||
</div>
|
||||
</form>
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue