Compare commits
8 Commits
e78324e92a
...
3f340d57fc
Author | SHA1 | Date | |
---|---|---|---|
3f340d57fc | |||
995cc32578 | |||
5f5b443df7 | |||
e5366171fd | |||
1c650fbb64 | |||
a784d44d16 | |||
e27aa8969f | |||
433ad39a08 |
@ -4,7 +4,7 @@ APP_KEY=base64:hSCTwZ507IdKQ5QJHJ+mQw0DSMgDdAspasjwHCdiB8Y=
|
|||||||
APP_DEBUG=true
|
APP_DEBUG=true
|
||||||
APP_DOMAIN=localhost
|
APP_DOMAIN=localhost
|
||||||
APP_URL="https://${APP_DOMAIN}"
|
APP_URL="https://${APP_DOMAIN}"
|
||||||
APP_UID_BYTES=8
|
ADMIN_EMAIL=""
|
||||||
|
|
||||||
GIT_HASH="00000000"
|
GIT_HASH="00000000"
|
||||||
GIT_TAG="x.x.x"
|
GIT_TAG="x.x.x"
|
||||||
@ -28,6 +28,9 @@ FILESYSTEM_DRIVER=local
|
|||||||
QUEUE_CONNECTION=sync
|
QUEUE_CONNECTION=sync
|
||||||
SESSION_DRIVER=database
|
SESSION_DRIVER=database
|
||||||
SESSION_LIFETIME=120
|
SESSION_LIFETIME=120
|
||||||
|
#SESSION_STORE=redis
|
||||||
|
#SESSION_DOMAIN="${APP_DOMAIN}"
|
||||||
|
#SESSION_SECURE_COOKIE=true
|
||||||
|
|
||||||
MEMCACHED_HOST=memcache
|
MEMCACHED_HOST=memcache
|
||||||
|
|
||||||
@ -35,6 +38,15 @@ REDIS_HOST=redis
|
|||||||
REDIS_PASSWORD=null
|
REDIS_PASSWORD=null
|
||||||
REDIS_PORT=6379
|
REDIS_PORT=6379
|
||||||
|
|
||||||
|
MAIL_MAILER=smtp
|
||||||
|
MAIL_HOST=mailhog
|
||||||
|
MAIL_PORT=1125
|
||||||
|
MAIL_USERNAME=null
|
||||||
|
MAIL_PASSWORD=null
|
||||||
|
MAIL_ENCRYPTION=null
|
||||||
|
MAIL_FROM_ADDRESS="hello@${APP_DOMAIN}"
|
||||||
|
MAIL_FROM_NAME="${APP_NAME}"
|
||||||
|
|
||||||
SCOUT_DRIVER=meilisearch
|
SCOUT_DRIVER=meilisearch
|
||||||
SCOUT_PREFIX=
|
SCOUT_PREFIX=
|
||||||
SCOUT_QUEUE=false
|
SCOUT_QUEUE=false
|
||||||
@ -43,14 +55,15 @@ MEILISEARCH_KEY=
|
|||||||
MEILISEARCH_PRIVATE_KEY=
|
MEILISEARCH_PRIVATE_KEY=
|
||||||
MEILISEARCH_PUBLIC_KEY=
|
MEILISEARCH_PUBLIC_KEY=
|
||||||
|
|
||||||
MAIL_MAILER=smtp
|
GOOGLE_GEOCODE_API_KEY=
|
||||||
MAIL_HOST=mailhog
|
|
||||||
MAIL_PORT=1125
|
MINIO_USERNAME=
|
||||||
MAIL_USERNAME=null
|
MINIO_PASSWORD=
|
||||||
MAIL_PASSWORD=null
|
MINIO_DEFAULT_REGION=us-west-1
|
||||||
MAIL_ENCRYPTION=null
|
MINIO_BUCKET=
|
||||||
MAIL_FROM_ADDRESS="no-reply@${APP_DOMAIN}"
|
MINIO_URL=
|
||||||
MAIL_FROM_NAME="${APP_NAME}"
|
MINIO_ENDPOINT=
|
||||||
|
MINIO_USE_PATH_STYLE_ENDPOINT=false
|
||||||
|
|
||||||
AWS_ACCESS_KEY_ID=
|
AWS_ACCESS_KEY_ID=
|
||||||
AWS_SECRET_ACCESS_KEY=
|
AWS_SECRET_ACCESS_KEY=
|
||||||
@ -84,3 +97,13 @@ INTEGRITY_HASH_WEBMANIFEST_JSON=""
|
|||||||
INTEGRITY_HASH_MIX_MANIFEST_JSON=""
|
INTEGRITY_HASH_MIX_MANIFEST_JSON=""
|
||||||
INTEGRITY_HASH_APP_CSS=""
|
INTEGRITY_HASH_APP_CSS=""
|
||||||
INTEGRITY_HASH_APP_JS=""
|
INTEGRITY_HASH_APP_JS=""
|
||||||
|
|
||||||
|
## Clockwork debug helpers
|
||||||
|
## default values are set, except for the main enable switch
|
||||||
|
CLOCKWORK_ENABLE=true
|
||||||
|
CLOCKWORK_WEB=true
|
||||||
|
CLOCKWORK_AUTHENTICATION=false
|
||||||
|
#CLOCKWORK_AUTHENTICATION_PASSWORD=VerySecretPassword
|
||||||
|
CLOCKWORK_CACHE_COLLECT_VALUES=true
|
||||||
|
#CLOCKWORK_DATABASE_SLOW_THRESHOLD= # time in miliseconds
|
||||||
|
#CLOCKWORK_REQUESTS_SLOW_THRESHOLD= # time in miliseconds
|
||||||
|
83
src/app/Console/Commands/TranslationCheckerCommand.php
Normal file
83
src/app/Console/Commands/TranslationCheckerCommand.php
Normal file
@ -0,0 +1,83 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Console\Commands;
|
||||||
|
|
||||||
|
use Illuminate\Console\Command;
|
||||||
|
|
||||||
|
class TranslationCheckerCommand extends Command
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* The name and signature of the console command.
|
||||||
|
*
|
||||||
|
* @var string
|
||||||
|
*/
|
||||||
|
protected $signature = 'i18n:check-json {--l|locale= : The source of truth locale}';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The console command description.
|
||||||
|
*
|
||||||
|
* @var string
|
||||||
|
*/
|
||||||
|
protected $description = 'Uses one JSON file as the source of truth to check other lang files against.';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Execute the console command.
|
||||||
|
*
|
||||||
|
* @return int
|
||||||
|
*/
|
||||||
|
public function handle()
|
||||||
|
{
|
||||||
|
$masterLocale = 'en';
|
||||||
|
if (!empty($this->option('locale'))) {
|
||||||
|
$masterLocale = trim($this->option('locale'));
|
||||||
|
}
|
||||||
|
|
||||||
|
$langDir = base_path('lang');
|
||||||
|
$langFiles = scandir($langDir);
|
||||||
|
$bigCount = count($langFiles);
|
||||||
|
for ($i = 0; $i < $bigCount; $i++) {
|
||||||
|
if (! preg_match('/.*\.json$/', $langFiles[$i]) || "{$masterLocale}.json" === $langFiles[$i]) {
|
||||||
|
unset($langFiles[$i]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$langFilesCount = count($langFiles);
|
||||||
|
$this->info("Checking {$langFilesCount} file(s) against {$masterLocale}...");
|
||||||
|
|
||||||
|
$masterLocaleTranslations = translations(base_path("lang/{$masterLocale}.json"));
|
||||||
|
|
||||||
|
foreach ($langFiles as $localeFile) {
|
||||||
|
$otherLocaleTranslations = translations(base_path("lang/{$localeFile}"));
|
||||||
|
$counts = 0;
|
||||||
|
$mergedTranslations = $this->recursiveMergeArray($masterLocaleTranslations, $otherLocaleTranslations, $counts);
|
||||||
|
file_put_contents(base_path("lang/{$localeFile}"), json_encode($mergedTranslations, JSON_UNESCAPED_UNICODE|JSON_PRETTY_PRINT));
|
||||||
|
$this->info("{$localeFile} had {$counts} missing translations.");
|
||||||
|
}
|
||||||
|
|
||||||
|
return Command::SUCCESS;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function recursiveMergeArray(array $firstArray, array $secondArray, int &$counts)
|
||||||
|
{
|
||||||
|
$mergedArray = $secondArray;
|
||||||
|
foreach ($firstArray as $key => $value) {
|
||||||
|
if (is_array($value)) {
|
||||||
|
if (!array_key_exists($key, $secondArray)) {
|
||||||
|
$mergedArray[$key] = $value;
|
||||||
|
} else {
|
||||||
|
$subDiff = $this->recursiveMergeArray($value, $secondArray[$key], $counts);
|
||||||
|
if (!empty($subDiff)) {
|
||||||
|
$mergedArray[$key] = $subDiff;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if (!array_key_exists($key, $secondArray)) {
|
||||||
|
$counts++;
|
||||||
|
$mergedArray[$key] = $value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return $mergedArray;
|
||||||
|
}
|
||||||
|
}
|
33
src/app/Http/Kernel.php
Normal file
33
src/app/Http/Kernel.php
Normal file
@ -0,0 +1,33 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Http;
|
||||||
|
|
||||||
|
use Illuminate\Foundation\Http\Kernel as HttpKernel;
|
||||||
|
|
||||||
|
class Kernel extends HttpKernel
|
||||||
|
{
|
||||||
|
//...
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The application's route middleware groups.
|
||||||
|
*
|
||||||
|
* @var array<string, array<int, class-string|string>>
|
||||||
|
*/
|
||||||
|
protected $middlewareGroups = [
|
||||||
|
'web' => [
|
||||||
|
//...
|
||||||
|
\Illuminate\Session\Middleware\StartSession::class,
|
||||||
|
\App\Http\Middleware\SetLocale::class,
|
||||||
|
\Illuminate\View\Middleware\ShareErrorsFromSession::class,
|
||||||
|
//...
|
||||||
|
],
|
||||||
|
|
||||||
|
'admin' => [
|
||||||
|
//...
|
||||||
|
\Laravel\Jetstream\Http\Middleware\ShareInertiaData::class,
|
||||||
|
\App\Http\Middleware\SetLocale::class,
|
||||||
|
\App\Http\Middleware\AuthorizeAdmin::class,
|
||||||
|
//...
|
||||||
|
],
|
||||||
|
];
|
||||||
|
}
|
@ -5,22 +5,20 @@ namespace App\Http\Middleware;
|
|||||||
use Closure;
|
use Closure;
|
||||||
use Illuminate\Http\Request;
|
use Illuminate\Http\Request;
|
||||||
|
|
||||||
class CheckCustomSessionData
|
class AuthorizeAdmin
|
||||||
{
|
{
|
||||||
/**
|
/**
|
||||||
* Handle an incoming request.
|
* Handle an incoming request.
|
||||||
*
|
*
|
||||||
* @since 1.0.0
|
|
||||||
*
|
|
||||||
* @param \Illuminate\Http\Request $request
|
* @param \Illuminate\Http\Request $request
|
||||||
* @param \Closure $next
|
* @param \Closure(\Illuminate\Http\Request): (\Illuminate\Http\Response|\Illuminate\Http\RedirectResponse) $next
|
||||||
*
|
*
|
||||||
* @return mixed
|
* @return \Illuminate\Http\Response|\Illuminate\Http\RedirectResponse
|
||||||
*/
|
*/
|
||||||
public function handle(Request $request, Closure $next)
|
public function handle(Request $request, Closure $next)
|
||||||
{
|
{
|
||||||
if ((! session()->has('thing') || empty(session('thing'))) && $request->user()) {
|
if ($request->user()->email !== env('ADMIN_EMAIL')) {
|
||||||
session()->put('thing', $request->user()->thing);
|
abort(HTTP_NOT_FOUND);
|
||||||
}
|
}
|
||||||
|
|
||||||
return $next($request);
|
return $next($request);
|
@ -2,6 +2,7 @@
|
|||||||
|
|
||||||
namespace App\Http\Middleware;
|
namespace App\Http\Middleware;
|
||||||
|
|
||||||
|
use App\Models\Language;
|
||||||
use Illuminate\Http\Request;
|
use Illuminate\Http\Request;
|
||||||
use Inertia\Middleware;
|
use Inertia\Middleware;
|
||||||
|
|
||||||
@ -41,19 +42,56 @@ class HandleInertiaRequests extends Middleware
|
|||||||
*/
|
*/
|
||||||
public function share(Request $request): array
|
public function share(Request $request): array
|
||||||
{
|
{
|
||||||
|
$localeFields = ['locale', 'iso_code', 'name', 'localized_name'];
|
||||||
|
$currentLocale = $request->session()->get('locale', null);
|
||||||
|
if (is_null($currentLocale)) {
|
||||||
|
$currentLocale = Language::where(['locale' => 'en', 'iso_code' => 'en_US'])->get($localeFields)[0]->toArray();
|
||||||
|
$request->session()->put('locale', [
|
||||||
|
'locale' => $currentLocale['locale'],
|
||||||
|
'iso_code' => $currentLocale['iso_code'],
|
||||||
|
'name' => $currentLocale['name'],
|
||||||
|
'localized_name' => $currentLocale['localized_name'],
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
$localeFilePath = base_path("lang/{$currentLocale['locale']}.json");
|
||||||
|
|
||||||
$notifications = [];
|
$notifications = [];
|
||||||
|
$notificationsCount = count($notifications);
|
||||||
$unreadNotifications = false;
|
$unreadNotifications = false;
|
||||||
if (! is_null($request->user())) {
|
if (! is_null($request->user())) {
|
||||||
$notifications = $request->user()->notifications;
|
$notifications = $request->user()->notifications;
|
||||||
|
$notificationsCount = count($notifications);
|
||||||
|
for ($i = 0; $i < $notificationsCount; $i++) {
|
||||||
|
$newData = $notifications[$i]->data;
|
||||||
|
$createdAt = carbon($notifications[$i]->created_at);
|
||||||
|
$dateFormat = 'F j';
|
||||||
|
if (!$createdAt->is(gmdate('Y'))) {
|
||||||
|
$dateFormat = 'F j, Y';
|
||||||
|
}
|
||||||
|
$newData['created_at_date'] = $createdAt->copy()->format($dateFormat);
|
||||||
|
$newData['created_at_time'] = $createdAt->copy()->format('H:i');
|
||||||
|
$notifications[$i]->data = $newData;
|
||||||
|
}
|
||||||
if (count($request->user()->unreadNotifications) > 0) {
|
if (count($request->user()->unreadNotifications) > 0) {
|
||||||
$unreadNotifications = true;
|
$unreadNotifications = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return array_merge(parent::share($request), [
|
$additionalData = [
|
||||||
'appName' => config('app.name'),
|
'appName' => config('app.name'),
|
||||||
|
|
||||||
|
'availableLocales' => Language::get($localeFields),
|
||||||
|
'currentLocale' => $currentLocale,
|
||||||
|
'language' => translations($localeFilePath),
|
||||||
|
|
||||||
'notifications' => $notifications,
|
'notifications' => $notifications,
|
||||||
'unreadNotifications' => $unreadNotifications,
|
'unreadNotifications' => $unreadNotifications,
|
||||||
]);
|
];
|
||||||
|
|
||||||
|
if ($request->user()->email === env('ADMIN_EMAIL')) {
|
||||||
|
$additionalData['is_admin_user'] = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return array_merge(parent::share($request), $additionalData);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
29
src/app/Http/Middleware/SetLocale.php
Normal file
29
src/app/Http/Middleware/SetLocale.php
Normal file
@ -0,0 +1,29 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Http\Middleware;
|
||||||
|
|
||||||
|
use Closure;
|
||||||
|
use Illuminate\Http\Request;
|
||||||
|
|
||||||
|
class SetLocale
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Handle an incoming request.
|
||||||
|
*
|
||||||
|
* @package App\Http\Middleware\SetLocale
|
||||||
|
* @since 1.0.0
|
||||||
|
*
|
||||||
|
* @param \Illuminate\Http\Request $request
|
||||||
|
* @param \Closure(\Illuminate\Http\Request): (\Illuminate\Http\Response|\Illuminate\Http\RedirectResponse) $next
|
||||||
|
*
|
||||||
|
* @return \Illuminate\Http\Response|\Illuminate\Http\RedirectResponse
|
||||||
|
*/
|
||||||
|
public function handle(Request $request, Closure $next)
|
||||||
|
{
|
||||||
|
if (session()->has('locale')) {
|
||||||
|
app()->setLocale(session('locale')['locale']);
|
||||||
|
}
|
||||||
|
|
||||||
|
return $next($request);
|
||||||
|
}
|
||||||
|
}
|
23
src/app/Models/Scopes/OnTeamScope.php
Normal file
23
src/app/Models/Scopes/OnTeamScope.php
Normal file
@ -0,0 +1,23 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Models\Scopes;
|
||||||
|
|
||||||
|
use Illuminate\Database\Eloquent\Builder;
|
||||||
|
use Illuminate\Database\Eloquent\Model;
|
||||||
|
use Illuminate\Database\Eloquent\Scope;
|
||||||
|
|
||||||
|
class OnTeamScope implements Scope
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Apply the scope to a given Eloquent query builder.
|
||||||
|
*
|
||||||
|
* @param \Illuminate\Database\Eloquent\Builder $builder
|
||||||
|
* @param \Illuminate\Database\Eloquent\Model $model
|
||||||
|
*
|
||||||
|
* @return void
|
||||||
|
*/
|
||||||
|
public function apply(Builder $builder, Model $model): void
|
||||||
|
{
|
||||||
|
$builder->where('current_team_id', );
|
||||||
|
}
|
||||||
|
}
|
@ -37,6 +37,7 @@ class User extends Authenticatable
|
|||||||
'name',
|
'name',
|
||||||
'surname',
|
'surname',
|
||||||
'timezone_name',
|
'timezone_name',
|
||||||
|
'language_id',
|
||||||
'current_team_id',
|
'current_team_id',
|
||||||
'profile_photo_path',
|
'profile_photo_path',
|
||||||
'email',
|
'email',
|
||||||
@ -183,4 +184,17 @@ class User extends Authenticatable
|
|||||||
{
|
{
|
||||||
return $this->morphOne(Address::class, 'addressable');
|
return $this->morphOne(Address::class, 'addressable');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Language relationship.
|
||||||
|
*
|
||||||
|
* @package App\Models\User
|
||||||
|
* @since 1.0.0
|
||||||
|
*
|
||||||
|
* @return \Illuminate\Database\Eloquent\Relations\BelongsTo
|
||||||
|
*/
|
||||||
|
public function language(): BelongsTo
|
||||||
|
{
|
||||||
|
return $this->belongsTo(Language::class);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -2,6 +2,7 @@
|
|||||||
|
|
||||||
namespace Database\Factories;
|
namespace Database\Factories;
|
||||||
|
|
||||||
|
use App\Models\Language;
|
||||||
use App\Models\Team;
|
use App\Models\Team;
|
||||||
use App\Models\User;
|
use App\Models\User;
|
||||||
use Illuminate\Database\Eloquent\Factories\Factory;
|
use Illuminate\Database\Eloquent\Factories\Factory;
|
||||||
@ -31,6 +32,7 @@ class UserFactory extends Factory
|
|||||||
'email' => $this->faker->unique()->safeEmail(),
|
'email' => $this->faker->unique()->safeEmail(),
|
||||||
'email_verified_at' => now(),
|
'email_verified_at' => now(),
|
||||||
'timezone_name' => $this->faker->timezone(),
|
'timezone_name' => $this->faker->timezone(),
|
||||||
|
'timezone_name' => Language::all()->random()->id,
|
||||||
'password' => '$2y$10$92IXUNpkjO0rOQ5byMi.Ye4oKoEa3Ro9llC/.og/at2.uheWG/igi', // password
|
'password' => '$2y$10$92IXUNpkjO0rOQ5byMi.Ye4oKoEa3Ro9llC/.og/at2.uheWG/igi', // password
|
||||||
'remember_token' => Str::random(10),
|
'remember_token' => Str::random(10),
|
||||||
];
|
];
|
||||||
|
@ -16,49 +16,18 @@ class LanguageSeeder extends Seeder
|
|||||||
*/
|
*/
|
||||||
public function run()
|
public function run()
|
||||||
{
|
{
|
||||||
$languages = [
|
$languagesData = [
|
||||||
[
|
['locale' => 'en', 'iso_code' => 'en_US', 'name' => 'English', 'localized_name' => 'English (US)'],
|
||||||
'iso_code' => 'en_US',
|
['locale' => 'de', 'iso_code' => 'de_DE', 'name' => 'German', 'localized_name' => 'Deutsch'],
|
||||||
'locale' => 'en',
|
['locale' => 'fr', 'iso_code' => 'fr_FR', 'name' => 'French', 'localized_name' => 'Français'],
|
||||||
'title' => 'English',
|
['locale' => 'jp', 'iso_code' => 'jp_JP', 'name' => 'Japanese', 'localized_name' => '日本'],
|
||||||
'title_localized' => 'English',
|
['locale' => 'mx', 'iso_code' => 'es_MX', 'name' => 'Spanish', 'localized_name' => 'Español'],
|
||||||
],
|
|
||||||
[
|
|
||||||
'iso_code' => 'de_DE',
|
|
||||||
'locale' => 'de',
|
|
||||||
'title' => 'German',
|
|
||||||
'title_localized' => 'Deutsch',
|
|
||||||
],
|
|
||||||
[
|
|
||||||
'iso_code' => 'fr_FR',
|
|
||||||
'locale' => 'fr',
|
|
||||||
'title' => 'French',
|
|
||||||
'title_localized' => 'Français',
|
|
||||||
],
|
|
||||||
[
|
|
||||||
'iso_code' => 'es_SP',
|
|
||||||
'locale' => 'es',
|
|
||||||
'title' => 'Spanish',
|
|
||||||
'title_localized' => 'Español',
|
|
||||||
],
|
|
||||||
[
|
|
||||||
'iso_code' => 'jp_JP',
|
|
||||||
'locale' => 'jp',
|
|
||||||
'title' => 'Japanese',
|
|
||||||
'title_localized' => '日本',
|
|
||||||
],
|
|
||||||
[
|
|
||||||
'iso_code' => 'zh_TW',
|
|
||||||
'locale' => 'zh',
|
|
||||||
'title' => 'Taiwanese',
|
|
||||||
'title_localized' => '台湾',
|
|
||||||
],
|
|
||||||
];
|
];
|
||||||
|
|
||||||
$startTime = Carbon::now();
|
$startTime = Carbon::now();
|
||||||
$offset = 0;
|
$offset = 0;
|
||||||
|
|
||||||
foreach ($languages as $language) {
|
foreach ($languagesData as $language) {
|
||||||
$datetime = $startTime->copy()->addMinute($offset)->toDateTimeString();
|
$datetime = $startTime->copy()->addMinute($offset)->toDateTimeString();
|
||||||
$offset++;
|
$offset++;
|
||||||
$language['created_at'] = $datetime;
|
$language['created_at'] = $datetime;
|
||||||
|
@ -1,3 +1,88 @@
|
|||||||
{
|
{
|
||||||
"key": "translation that has :attribute in the string."
|
"titles": {
|
||||||
|
"welcome": "Welcome",
|
||||||
|
"dashboard": "Dashboard",
|
||||||
|
"secure_area": "Secure Area",
|
||||||
|
"forgot_password": "Forgot Password",
|
||||||
|
"my_profile": "My Profile",
|
||||||
|
"edit_profile": "Edit Profile",
|
||||||
|
"log_in": "Log In To Your Account",
|
||||||
|
"register": "Register A New Account",
|
||||||
|
"reset_password": "Reset Password",
|
||||||
|
"two_factor_confirmation": "Two-factor Confirmation",
|
||||||
|
"verify_email": "Email Verification"
|
||||||
|
},
|
||||||
|
"labels": {
|
||||||
|
"type": "Type",
|
||||||
|
"none": "None",
|
||||||
|
"title": "Title",
|
||||||
|
"name": "Name",
|
||||||
|
"created_at": "Created At",
|
||||||
|
"updated_at": "Updated At",
|
||||||
|
"actions": "Actions",
|
||||||
|
"email": "Email",
|
||||||
|
"timezone_name": "Timezone",
|
||||||
|
"language": "Language",
|
||||||
|
"password": "Password",
|
||||||
|
"confirm_password": "Confirm Password",
|
||||||
|
"current_password": "Current Password",
|
||||||
|
"remember_me": "Remember me",
|
||||||
|
"first_name": "First Name",
|
||||||
|
"last_name": "Surname",
|
||||||
|
"forgot_password": "Forgot your password?",
|
||||||
|
"already_registered": "Already registered?",
|
||||||
|
"code": "Code",
|
||||||
|
"recovery_code": "Recovery Code",
|
||||||
|
"use_recovery_code": "Use a recovery code",
|
||||||
|
"use_authentication_code": "Use an authentication code",
|
||||||
|
"email_verification": "Email Verification",
|
||||||
|
"user": "User",
|
||||||
|
"optional": "optional"
|
||||||
|
},
|
||||||
|
"actions": {
|
||||||
|
"save": "Save",
|
||||||
|
"update": "Update",
|
||||||
|
"edit": "Edit",
|
||||||
|
"delete": "Delete",
|
||||||
|
"confirm": "Confirm",
|
||||||
|
"log_in": "Log in",
|
||||||
|
"log_out": "Log Out",
|
||||||
|
"register": "Register",
|
||||||
|
"edit_profile": "Edit Profile",
|
||||||
|
"confirm_password": "Confirm Password",
|
||||||
|
"reset_password": "Reset Password",
|
||||||
|
"email_password_reset_link": "Email Password Reset Link",
|
||||||
|
"resend_verification_email": "Resend Verification Email"
|
||||||
|
},
|
||||||
|
"nav": {
|
||||||
|
"manage_team": "Manage Team",
|
||||||
|
"team_settings": "Team Settings",
|
||||||
|
"create_new_team": "Create New Team",
|
||||||
|
"switch_teams": "Switch Teams",
|
||||||
|
"manage_account": "Manage Account",
|
||||||
|
"profile": "My Profile",
|
||||||
|
"admin_settings": "Admin Settings",
|
||||||
|
"api_tokens": "API Tokens"
|
||||||
|
},
|
||||||
|
"pagination": {
|
||||||
|
"show_from_to_count": "Showing :from to :to of :count results"
|
||||||
|
},
|
||||||
|
"notifications": {
|
||||||
|
"saved": "Saved.",
|
||||||
|
"verified": "Verified!",
|
||||||
|
"unverified": "Unverified.",
|
||||||
|
"new_email_verification_sent": "A new verification link has been sent to the email address you provided in your profile settings."
|
||||||
|
},
|
||||||
|
"pages": {
|
||||||
|
"profile": {
|
||||||
|
"titles": {
|
||||||
|
"update_password": "Update Your Password"
|
||||||
|
},
|
||||||
|
"text_blocks": {
|
||||||
|
"unverified_message": "Please verify your account.",
|
||||||
|
"verified_message": "Thanks for being amazing.",
|
||||||
|
"secure_password": "Ensure your account is using a long, random password to stay secure."
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,28 +1,24 @@
|
|||||||
{
|
{
|
||||||
"private": true,
|
"private": true,
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"dev": "npm run development",
|
"dev": "vite",
|
||||||
"development": "mix",
|
"build": "vite build"
|
||||||
"watch": "mix watch",
|
|
||||||
"watch-poll": "mix watch -- --watch-options-poll=1000",
|
|
||||||
"hot": "mix watch --hot",
|
|
||||||
"prod": "npm run production",
|
|
||||||
"production": "mix --production"
|
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"laravel-mix": "^6.0.49",
|
"@vitejs/plugin-vue": "^3.0.0",
|
||||||
|
"laravel-vite-plugin": "^0.6.0",
|
||||||
"postcss": "^8.4.14",
|
"postcss": "^8.4.14",
|
||||||
"postcss-import": "^14.1.0",
|
"vite": "^3.0.0"
|
||||||
"vue-loader": "^17.0.0"
|
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@inertiajs/inertia": "^0.11.0",
|
"@inertiajs/inertia": "^0.11.0",
|
||||||
"@inertiajs/inertia-vue3": "^0.6.0",
|
"@inertiajs/inertia-vue3": "^0.6.0",
|
||||||
"@inertiajs/progress": "^0.2.7",
|
"@inertiajs/progress": "^0.2.7",
|
||||||
"@tailwindcss/aspect-ratio": "^0.4.0",
|
"@tailwindcss/aspect-ratio": "^0.4.0",
|
||||||
"@tailwindcss/forms": "^0.5.2",
|
"@tailwindcss/line-clamp": "^0.4.2",
|
||||||
"@tailwindcss/typography": "^0.5.2",
|
"@tailwindcss/typography": "^0.5.2",
|
||||||
"@vueuse/core": "^8.7.5",
|
"@vueuse/core": "^8.7.5",
|
||||||
|
"apexcharts": "^3.35.5",
|
||||||
"axios": "^0.27.2",
|
"axios": "^0.27.2",
|
||||||
"daisyui": "^2.29.0",
|
"daisyui": "^2.29.0",
|
||||||
"dayjs": "^1.11.3",
|
"dayjs": "^1.11.3",
|
||||||
|
6
src/postcss.config.js
Normal file
6
src/postcss.config.js
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
module.exports = {
|
||||||
|
plugins: {
|
||||||
|
tailwindcss: {},
|
||||||
|
autoprefixer: {},
|
||||||
|
},
|
||||||
|
};
|
59
src/resources/css/animations/bubbleup.css
Normal file
59
src/resources/css/animations/bubbleup.css
Normal file
@ -0,0 +1,59 @@
|
|||||||
|
/**
|
||||||
|
<div class="bubbles-container">
|
||||||
|
<div class="bubbles">
|
||||||
|
<span class="animate-bubble" style="style="--i:(random value between 1 and 15);"></span>
|
||||||
|
<span class="animate-bubble" style="style="--i:(random value between 1 and 15);"></span>
|
||||||
|
<span class="animate-bubble" style="style="--i:(random value between 1 and 15);"></span>
|
||||||
|
<span class="animate-bubble" style="style="--i:(random value between 1 and 15);"></span>
|
||||||
|
<span class="animate-bubble" style="style="--i:(random value between 1 and 15);"></span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
**/
|
||||||
|
|
||||||
|
.bubbles-container {
|
||||||
|
background: hsl(216, 57.1%, 11%);
|
||||||
|
min-height: 100%;
|
||||||
|
overflow: hidden;
|
||||||
|
position: relative;
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.bubbles {
|
||||||
|
position: relative;
|
||||||
|
display: flex;
|
||||||
|
}
|
||||||
|
|
||||||
|
.bubbles span {
|
||||||
|
border-radius: 50%;
|
||||||
|
height: 30px;
|
||||||
|
margin: 0 4px;
|
||||||
|
position: relative;
|
||||||
|
width: 30px;
|
||||||
|
/*animation: bubble 15s linear infinite;*/
|
||||||
|
/*animation-name: bubbleup;
|
||||||
|
animation-timing-function: linear;
|
||||||
|
animation-iteration-count: infinite;
|
||||||
|
animation-duration: calc(300s / calc(var(--i) * 5));*/
|
||||||
|
}
|
||||||
|
|
||||||
|
.bubbles span:nth-child(odd) {
|
||||||
|
background: hsl(191, 66.8%, 58.6%);
|
||||||
|
box-shadow: 0 0 0 10px hsla(191, 66.8%, 58.6%, 0.3), 0 0 50px hsl(191, 66.8%, 58.6%), 0 0 100px hsl(191, 66.8%, 58.6%);
|
||||||
|
}
|
||||||
|
|
||||||
|
.bubbles span:nth-child(even) {
|
||||||
|
background: hsl(339, 100%, 58.8%);
|
||||||
|
box-shadow: 0 0 0 10px hsla(339, 100%, 58.8%, 0.3), 0 0 50px hsl(339, 100%, 58.8%), 0 0 100px hsl(339, 100%, 58.8%);
|
||||||
|
}
|
||||||
|
|
||||||
|
.animate-bubble {
|
||||||
|
animation-name: bubbleup;
|
||||||
|
animation-duration: calc(300s / calc(var(--i) * 5));
|
||||||
|
animation-timing-function: linear;
|
||||||
|
animation-iteration-count: infinite;
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes bubbleup {
|
||||||
|
0% { transform: translateY(100px) scale(0); }
|
||||||
|
100% { transform: translateY(-100px) scale(1); }
|
||||||
|
}
|
93
src/resources/js/Components/BellNotifications.vue
Normal file
93
src/resources/js/Components/BellNotifications.vue
Normal file
@ -0,0 +1,93 @@
|
|||||||
|
<script setup>
|
||||||
|
import { ref } from 'vue'
|
||||||
|
import { usePage } from '@inertiajs/inertia-vue3'
|
||||||
|
import Dropdown from './Dropdown.vue'
|
||||||
|
import GhostButton from '@/Components/Buttons/GhostButton.vue'
|
||||||
|
import IconBell from '@/Icons/Bell.vue'
|
||||||
|
import Modal from '@/Components/Modals/Modal.vue'
|
||||||
|
|
||||||
|
// variables
|
||||||
|
const notifications = ref(usePage().props.value.notifications)
|
||||||
|
const hasUnreadNotifications = ref(usePage().props.value.unreadNotifications)
|
||||||
|
const showNotification = ref(false)
|
||||||
|
const activeNotification = ref({})
|
||||||
|
|
||||||
|
// methods
|
||||||
|
const openNotification = (notification) => {
|
||||||
|
activeNotification.value = notification
|
||||||
|
showNotification.value = true
|
||||||
|
markActiveAsRead()
|
||||||
|
}
|
||||||
|
|
||||||
|
const closeModal = () => {
|
||||||
|
showNotification.value = false
|
||||||
|
activeNotification.value = {}
|
||||||
|
}
|
||||||
|
|
||||||
|
const markActiveAsRead = () => {
|
||||||
|
axios.post(route('api.notifications.mark-read', activeNotification.value.id))
|
||||||
|
.then(resp => {
|
||||||
|
activeNotification.value.read_at = resp.data.read_at
|
||||||
|
})
|
||||||
|
.catch(erro => {
|
||||||
|
//console.error(erro)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
const markAllRead = () => {
|
||||||
|
axios.post(route('api.notifications.mark-all-read'))
|
||||||
|
.then(resp => {
|
||||||
|
let nowDt = new Date()
|
||||||
|
notifications.value.forEach(notification => {
|
||||||
|
if (notification.read_at === null) {
|
||||||
|
notification.read_at = nowDt
|
||||||
|
}
|
||||||
|
})
|
||||||
|
hasUnreadNotifications.value = false
|
||||||
|
})
|
||||||
|
.catch(erro => {
|
||||||
|
//console.error(erro)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<Dropdown align="right" width="80">
|
||||||
|
<template #trigger>
|
||||||
|
<GhostButton>
|
||||||
|
<IconBell></IconBell>
|
||||||
|
<div v-show="hasUnreadNotifications" class="absolute top-2 right-4 w-3 h-3 z-10 border-2 border-stone-100 bg-red-800 rounded-full"> </div>
|
||||||
|
</GhostButton>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<template #content>
|
||||||
|
<div class="w-full">
|
||||||
|
<div class="grid auto-rows-max gap-y-px bg-stone-400">
|
||||||
|
<template v-for="notification in notifications" :key="notification.id">
|
||||||
|
<div class="px-4 py-2 grid auto-rows-max gap-3 cursor-pointer" :class="{'bg-stone-200': notification.read_at !== null, 'bg-white': notification.read_at === null}" @click="openNotification(notification)">
|
||||||
|
<div>{{ notification.data.title }}</div>
|
||||||
|
<div class="text-xs text-zinc-600">{{ notification.data.created_at_date }}</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="border-t border-gray-100"></div>
|
||||||
|
|
||||||
|
<div class="px-2">
|
||||||
|
<button class="btn btn-ghost text-xs" @click="markAllRead">Mark All Read</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
</Dropdown>
|
||||||
|
|
||||||
|
<Modal :show="showNotification" @close="closeModal">
|
||||||
|
<div class="grid auto-rows-max">
|
||||||
|
<header class="bg-sky-800 text-white p-4">{{ activeNotification.data.title }}</header>
|
||||||
|
<div class="p-4 bg-white text-zinc-900">
|
||||||
|
<p class="mb-4">{{ activeNotification.data.body }}</p>
|
||||||
|
|
||||||
|
<p class="text-sm">{{ activeNotification.data.type }} | {{ activeNotification.data.created_at_date }} at {{ activeNotification.data.created_at_time }}</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</Modal>
|
||||||
|
</template>
|
32
src/resources/js/Components/Forms/CheckboxInput.vue
Normal file
32
src/resources/js/Components/Forms/CheckboxInput.vue
Normal file
@ -0,0 +1,32 @@
|
|||||||
|
<script setup>
|
||||||
|
import { computed } from 'vue'
|
||||||
|
|
||||||
|
// variables
|
||||||
|
const emit = defineEmits(['update:checked'])
|
||||||
|
|
||||||
|
const props = defineProps({
|
||||||
|
checked: {
|
||||||
|
type: [Array, Boolean],
|
||||||
|
default: false,
|
||||||
|
},
|
||||||
|
value: {
|
||||||
|
type: String,
|
||||||
|
default: null,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
// computed properties
|
||||||
|
const proxyChecked = computed({
|
||||||
|
get() {
|
||||||
|
return props.checked
|
||||||
|
},
|
||||||
|
|
||||||
|
set(val) {
|
||||||
|
emit('update:checked', val)
|
||||||
|
},
|
||||||
|
})
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<input type="checkbox" class="checkbox" :value="value" v-model="proxyChecked">
|
||||||
|
</template>
|
9
src/resources/js/Components/Forms/InputError.vue
Normal file
9
src/resources/js/Components/Forms/InputError.vue
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
<script setup>
|
||||||
|
defineProps({
|
||||||
|
message: String,
|
||||||
|
})
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<p v-show="message" class="px-2 py-1 text-sm text-red-600">{{ message }}</p>
|
||||||
|
</template>
|
20
src/resources/js/Components/Forms/InputLabel.vue
Normal file
20
src/resources/js/Components/Forms/InputLabel.vue
Normal file
@ -0,0 +1,20 @@
|
|||||||
|
<script setup>
|
||||||
|
import { useSlots, computed } from 'vue'
|
||||||
|
|
||||||
|
// defines
|
||||||
|
defineProps({
|
||||||
|
value: String,
|
||||||
|
})
|
||||||
|
|
||||||
|
// computed properties
|
||||||
|
const hasActions = computed(() => !! useSlots().actions)
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<label class="label">
|
||||||
|
<span class="label-text"><slot></slot></span>
|
||||||
|
<span v-show="hasActions" class="flex items-center">
|
||||||
|
<slot name="actions"></slot>
|
||||||
|
</span>
|
||||||
|
</label>
|
||||||
|
</template>
|
28
src/resources/js/Components/Forms/RadioInput.vue
Normal file
28
src/resources/js/Components/Forms/RadioInput.vue
Normal file
@ -0,0 +1,28 @@
|
|||||||
|
<script setup>
|
||||||
|
import { computed } from 'vue'
|
||||||
|
|
||||||
|
// variables
|
||||||
|
const emit = defineEmits(['update:checked'])
|
||||||
|
|
||||||
|
const props = defineProps({
|
||||||
|
checked: {
|
||||||
|
type: [Array, Boolean],
|
||||||
|
default: false,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
// computed properties
|
||||||
|
const proxyChecked = computed({
|
||||||
|
get() {
|
||||||
|
return props.checked
|
||||||
|
},
|
||||||
|
|
||||||
|
set(val) {
|
||||||
|
emit('update:checked', val)
|
||||||
|
},
|
||||||
|
})
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<input type="radio" class="radio" v-model="proxyChecked">
|
||||||
|
</template>
|
28
src/resources/js/Components/Forms/TextInput.vue
Normal file
28
src/resources/js/Components/Forms/TextInput.vue
Normal file
@ -0,0 +1,28 @@
|
|||||||
|
<script setup>
|
||||||
|
import { ref, onMounted } from 'vue'
|
||||||
|
|
||||||
|
// defines
|
||||||
|
defineEmits(['update:modelValue'])
|
||||||
|
|
||||||
|
defineProps({
|
||||||
|
modelValue: String,
|
||||||
|
})
|
||||||
|
|
||||||
|
defineExpose({
|
||||||
|
focus: () => input.value.focus()
|
||||||
|
})
|
||||||
|
|
||||||
|
// variables
|
||||||
|
const input = ref(null)
|
||||||
|
|
||||||
|
// lifecycle hooks
|
||||||
|
onMounted(() => {
|
||||||
|
if (input.value.hasAttribute('autofocus')) {
|
||||||
|
input.value.focus()
|
||||||
|
}
|
||||||
|
})
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<input ref="input" class="input" :value="modelValue" @input="$emit('update:modelValue', $event.target.value)">
|
||||||
|
</template>
|
30
src/resources/js/Components/Forms/Textarea.vue
Normal file
30
src/resources/js/Components/Forms/Textarea.vue
Normal file
@ -0,0 +1,30 @@
|
|||||||
|
<script setup>
|
||||||
|
import { ref, onMounted } from 'vue'
|
||||||
|
|
||||||
|
// defines
|
||||||
|
defineEmits(['update:modelValue'])
|
||||||
|
|
||||||
|
defineProps({
|
||||||
|
modelValue: String,
|
||||||
|
})
|
||||||
|
|
||||||
|
defineExpose({
|
||||||
|
focus: () => input.value.focus()
|
||||||
|
})
|
||||||
|
|
||||||
|
// variables
|
||||||
|
const input = ref(null)
|
||||||
|
|
||||||
|
// lifecycle hooks
|
||||||
|
onMounted(() => {
|
||||||
|
if (input.value.hasAttribute('autofocus')) {
|
||||||
|
input.value.focus()
|
||||||
|
}
|
||||||
|
})
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<textarea ref="input" class="textarea" :value="modelValue" @input="$emit('update:modelValue', $event.target.value)">
|
||||||
|
<slot></slot>
|
||||||
|
</textarea>
|
||||||
|
</template>
|
52
src/resources/js/Components/LanguageSwitcher.vue
Normal file
52
src/resources/js/Components/LanguageSwitcher.vue
Normal file
@ -0,0 +1,52 @@
|
|||||||
|
<script setup>
|
||||||
|
import { ref } from 'vue'
|
||||||
|
import { Inertia } from '@inertiajs/inertia'
|
||||||
|
import { usePage } from '@inertiajs/inertia-vue3'
|
||||||
|
import Dropdown from './DropdownMenu.vue'
|
||||||
|
|
||||||
|
// variables
|
||||||
|
const currentLocale = ref(usePage().props.value.currentLocale)
|
||||||
|
|
||||||
|
// methods
|
||||||
|
const isCurrent = (language) => {
|
||||||
|
return language.iso_code === currentLocale.value.iso_code
|
||||||
|
}
|
||||||
|
|
||||||
|
const changeLocale = (language) => {
|
||||||
|
if (language.iso_code !== currentLocale.value.iso_code) {
|
||||||
|
Inertia.post(route('locale.set'), language, {
|
||||||
|
replace: true,
|
||||||
|
onSuccess: page => {
|
||||||
|
currentLocale.value = page.props.currentLocale
|
||||||
|
},
|
||||||
|
onError: errors => {
|
||||||
|
console.log(errors)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<Dropdown align="right" width="60">
|
||||||
|
<template #trigger>
|
||||||
|
<div class="py-2 px-4 inline-grid grid-flow-col auto-cols-max gap-x-2 items-center bg-white hover:bg-gray-50 text-sm leading-4 font-medium rounded-md cursor-pointer">
|
||||||
|
<svg class="h-4 w-8">
|
||||||
|
<use :href="`#flag-${currentLocale.iso_code}`"></use>
|
||||||
|
</svg>
|
||||||
|
<span class="ml-2">{{ currentLocale.localized_name }}</span>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<template #content>
|
||||||
|
<div class="w-60">
|
||||||
|
<button type="button" v-for="(lang, langIdx) in $page.props.availableLocales" :key="langIdx" @click="changeLocale(lang)" class="py-2 px-4 inline-grid grid-flow-col auto-cols-max gap-x-2 items-center w-full" :class="{ 'text-white bg-sky-600 cursor-default': isCurrent(lang), 'hover:bg-sky-600/50': !isCurrent(lang) }">
|
||||||
|
<svg class="h-4 w-8">
|
||||||
|
<use :href="`#flag-${lang.iso_code}`"></use>
|
||||||
|
</svg>
|
||||||
|
<span class="ml-2">{{ lang.localized_name }}</span>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
</Dropdown>
|
||||||
|
</template>
|
@ -5,6 +5,7 @@ import { createInertiaApp } from '@inertiajs/inertia-vue3';
|
|||||||
import { InertiaProgress } from '@inertiajs/progress';
|
import { InertiaProgress } from '@inertiajs/progress';
|
||||||
import { resolvePageComponent } from 'laravel-vite-plugin/inertia-helpers';
|
import { resolvePageComponent } from 'laravel-vite-plugin/inertia-helpers';
|
||||||
import { ZiggyVue } from '../../vendor/tightenco/ziggy/dist/vue.m';
|
import { ZiggyVue } from '../../vendor/tightenco/ziggy/dist/vue.m';
|
||||||
|
import translationHelper from './base.js';
|
||||||
import Notifications from 'notiwind';
|
import Notifications from 'notiwind';
|
||||||
import AppLayout from './Layouts/AppLayout.vue';
|
import AppLayout from './Layouts/AppLayout.vue';
|
||||||
import AuthLayout from './Layouts/AuthLayout.vue';
|
import AuthLayout from './Layouts/AuthLayout.vue';
|
||||||
@ -43,6 +44,7 @@ createInertiaApp({
|
|||||||
.use(plugin)
|
.use(plugin)
|
||||||
.use(Notifications)
|
.use(Notifications)
|
||||||
.use(ZiggyVue, Ziggy)
|
.use(ZiggyVue, Ziggy)
|
||||||
|
.mixin(translationHelper)
|
||||||
.mixin({ methods: { route } })
|
.mixin({ methods: { route } })
|
||||||
.mount(el);
|
.mount(el);
|
||||||
},
|
},
|
||||||
|
30
src/resources/js/base.js
Normal file
30
src/resources/js/base.js
Normal file
@ -0,0 +1,30 @@
|
|||||||
|
export default {
|
||||||
|
methods: {
|
||||||
|
__(key, replace = {}) {
|
||||||
|
let translation = ''
|
||||||
|
|
||||||
|
// check for dot notation
|
||||||
|
if (key.includes('.')) {
|
||||||
|
translation = this.$page.props.language
|
||||||
|
key.split('.').forEach(subKey => {
|
||||||
|
translation = translation[subKey]
|
||||||
|
}, key)
|
||||||
|
} else {
|
||||||
|
// check if it exists on the translated strings we got
|
||||||
|
if (this.$page.props.language.hasOwnProperty(key)) {
|
||||||
|
translation = this.$page.props.language[key]
|
||||||
|
} else {
|
||||||
|
// otherwise just take it raw
|
||||||
|
translation = key
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// used to fill in variables for translation string
|
||||||
|
Object.keys(replace).forEach(key => {
|
||||||
|
translation = translation.replace(':' + key, replace[key])
|
||||||
|
});
|
||||||
|
|
||||||
|
return translation;
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
39
src/vite.config.js
Normal file
39
src/vite.config.js
Normal file
@ -0,0 +1,39 @@
|
|||||||
|
import fs from 'fs';
|
||||||
|
import { defineConfig, loadEnv } from 'vite';
|
||||||
|
import laravel from 'laravel-vite-plugin';
|
||||||
|
import vue from '@vitejs/plugin-vue';
|
||||||
|
|
||||||
|
export default ({ mode }) => {
|
||||||
|
process.env = Object.assign(process.env, loadEnv(mode, process.cwd(), ''));
|
||||||
|
|
||||||
|
return defineConfig({
|
||||||
|
build: {
|
||||||
|
reportCompressedSize: true,
|
||||||
|
},
|
||||||
|
plugins: [
|
||||||
|
laravel({
|
||||||
|
input: 'resources/js/app.js',
|
||||||
|
ssr: 'resources/js/ssr.js',
|
||||||
|
refresh: true,
|
||||||
|
}),
|
||||||
|
vue({
|
||||||
|
template: {
|
||||||
|
transformAssetUrls: {
|
||||||
|
base: null,
|
||||||
|
includeAbsolute: false,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
],
|
||||||
|
ssr: {
|
||||||
|
noExternal: ['@inertiajs/server'],
|
||||||
|
},
|
||||||
|
server: {
|
||||||
|
host: process.env.APP_DOMAIN,
|
||||||
|
https: {
|
||||||
|
key: fs.readFileSync(`/code/docker/configs/nginx/ssls/${process.env.APP_DOMAIN}/${process.env.APP_DOMAIN}.key`),
|
||||||
|
cert: fs.readFileSync(`/code/docker/configs/nginx/ssls/${process.env.APP_DOMAIN}/${process.env.APP_DOMAIN}.crt`),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
Loading…
x
Reference in New Issue
Block a user