Add (v1)
This commit is contained in:
@@ -4,6 +4,7 @@
|
|||||||
|
|
||||||
use App\Http\Controllers\Controller;
|
use App\Http\Controllers\Controller;
|
||||||
use App\Models\Post;
|
use App\Models\Post;
|
||||||
|
use App\Models\PostCategory;
|
||||||
use Illuminate\Http\Request;
|
use Illuminate\Http\Request;
|
||||||
|
|
||||||
class PostController extends Controller
|
class PostController extends Controller
|
||||||
@@ -27,10 +28,113 @@ public function edit(Request $request, $post_id)
|
|||||||
{
|
{
|
||||||
$post = Post::find($post_id);
|
$post = Post::find($post_id);
|
||||||
|
|
||||||
if (!is_null($post))
|
if (! is_null($post)) {
|
||||||
{
|
return view('admin.posts.upsert', compact('post'));
|
||||||
return view('admin.posts.upsert', compact('post'));
|
}
|
||||||
|
|
||||||
|
return redirect()->back()->with('error', 'Post does not exist.');
|
||||||
|
}
|
||||||
|
|
||||||
|
public function postUpsert(Request $request)
|
||||||
|
{
|
||||||
|
|
||||||
|
$post_data = [
|
||||||
|
'id' => $request->input('id', null),
|
||||||
|
'publish_date' => $request->input('publish_date', null),
|
||||||
|
'title' => $request->input('title'),
|
||||||
|
'slug' => $request->input('slug'),
|
||||||
|
'excerpt' => $request->input('excerpt'),
|
||||||
|
'author_id' => intval($request->input('author_id', 1)),
|
||||||
|
'featured' => filter_var($request->input('featured'), FILTER_VALIDATE_BOOLEAN, FILTER_NULL_ON_FAILURE),
|
||||||
|
'featured_image' => $request->input('featured_image'),
|
||||||
|
'editor' => 'editorjs',
|
||||||
|
'body' => json_decode($request->input('body')),
|
||||||
|
'post_format' => 'standard',
|
||||||
|
'comment_count' => 0,
|
||||||
|
'likes_count' => 0,
|
||||||
|
'status' => $request->input('status'),
|
||||||
|
];
|
||||||
|
|
||||||
|
$post_categories = $this->normalizeCategories($request->input('categories'));
|
||||||
|
|
||||||
|
if (! empty($post_data['id'])) {
|
||||||
|
// It's an update - find the existing post by its ID
|
||||||
|
$existingPost = Post::find($post_data['id']);
|
||||||
|
|
||||||
|
// Check if the post with the given ID exists
|
||||||
|
if ($existingPost) {
|
||||||
|
// Update the existing post with the new data
|
||||||
|
$existingPost->update($post_data);
|
||||||
|
|
||||||
|
// Handle PostCategory records for the existing post
|
||||||
|
$existingPostCategoryIds = $existingPost?->post_categories->pluck('id')->toArray();
|
||||||
|
|
||||||
|
// Find the IDs of PostCategory records that should be removed
|
||||||
|
$postCategoriesToRemove = array_diff($existingPostCategoryIds, $post_categories);
|
||||||
|
|
||||||
|
// Remove the unwanted PostCategory records
|
||||||
|
if (! empty($postCategoriesToRemove)) {
|
||||||
|
PostCategory::whereIn('id', $postCategoriesToRemove)->delete();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Find the new PostCategory records that should be added
|
||||||
|
$postCategoriesToAdd = array_diff($post_categories, $existingPostCategoryIds);
|
||||||
|
|
||||||
|
// Create the new PostCategory records
|
||||||
|
foreach ($postCategoriesToAdd as $categoryId) {
|
||||||
|
PostCategory::create([
|
||||||
|
'post_id' => $existingPost->id,
|
||||||
|
'category_id' => $categoryId,
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Return a response indicating a successful update
|
||||||
|
return response()->json(['message' => 'Post updated successfully', 'action' => 'redirect_back']);
|
||||||
|
} else {
|
||||||
|
// If the post with the given ID doesn't exist, you can handle the error as per your requirement
|
||||||
|
return response()->json(['error' => 'Post not found'], 404);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// It's a new post - create a new record using Post::create
|
||||||
|
$newPost = Post::create($post_data);
|
||||||
|
|
||||||
|
// Create the new PostCategory records for the new post
|
||||||
|
foreach ($post_categories as $categoryId) {
|
||||||
|
PostCategory::create([
|
||||||
|
'post_id' => $newPost->id,
|
||||||
|
'category_id' => $categoryId,
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Return a response indicating a successful creation
|
||||||
|
return response()->json(['message' => 'Post created successfully', 'action' => 'redirect_back']);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private function normalizeCategories($categories)
|
||||||
|
{
|
||||||
|
if (empty($categories) || is_null($categories)) {
|
||||||
|
// If the input is empty or null, return an empty array
|
||||||
|
return [];
|
||||||
|
} elseif (is_numeric($categories)) {
|
||||||
|
// If the input is a numeric value (integer or string), return an array with the integer value
|
||||||
|
return [(int) $categories];
|
||||||
|
} else {
|
||||||
|
// If the input is a string with separated commas or a JSON string that becomes an array of integers, return an array of integers
|
||||||
|
if (is_string($categories)) {
|
||||||
|
// Attempt to convert the string to an array of integers
|
||||||
|
$categoryIds = json_decode($categories, true);
|
||||||
|
|
||||||
|
// Check if the decoding was successful and the result is an array of integers
|
||||||
|
if (is_array($categoryIds) && ! empty($categoryIds)) {
|
||||||
|
$categoryIds = array_map('intval', $categoryIds);
|
||||||
|
|
||||||
|
return $categoryIds;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// If the input format doesn't match any of the above cases, return an empty array
|
||||||
|
return [];
|
||||||
}
|
}
|
||||||
return redirect()->back()->with('error','Post does not exist.');
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -8,10 +8,25 @@
|
|||||||
use App\Models\Post;
|
use App\Models\Post;
|
||||||
use Illuminate\Http\Request;
|
use Illuminate\Http\Request;
|
||||||
|
|
||||||
|
use Artesaos\SEOTools\Facades\SEOTools;
|
||||||
|
use Artesaos\SEOTools\Facades\SEOMeta;
|
||||||
|
use Artesaos\SEOTools\Facades\OpenGraph;
|
||||||
|
use Artesaos\SEOTools\Facades\JsonLd;
|
||||||
|
use Artesaos\SEOTools\Facades\JsonLdMulti;
|
||||||
|
|
||||||
|
|
||||||
class HomeController extends Controller
|
class HomeController extends Controller
|
||||||
{
|
{
|
||||||
public function index(Request $request)
|
public function index(Request $request)
|
||||||
{
|
{
|
||||||
|
|
||||||
|
SEOTools::metatags();
|
||||||
|
SEOTools::twitter();
|
||||||
|
SEOTools::opengraph();
|
||||||
|
SEOTools::jsonLd();
|
||||||
|
SEOTools::setTitle("Top Product Reviews, Deals & New Launches");
|
||||||
|
SEOTools::setDescription("Explore ProductAlert for in-depth product reviews and incredible deals. We cover Beauty, Tech, Home Appliances, Health & Fitness, Parenting, and more.");
|
||||||
|
|
||||||
$country = strtolower($request->session()->get('country'));
|
$country = strtolower($request->session()->get('country'));
|
||||||
|
|
||||||
return redirect()->route('home.country', ['country' => $country]);
|
return redirect()->route('home.country', ['country' => $country]);
|
||||||
@@ -19,6 +34,8 @@ public function index(Request $request)
|
|||||||
|
|
||||||
public function country(Request $request, $country)
|
public function country(Request $request, $country)
|
||||||
{
|
{
|
||||||
|
|
||||||
|
|
||||||
$country_locale = CountryLocale::where('slug', $country)->first();
|
$country_locale = CountryLocale::where('slug', $country)->first();
|
||||||
|
|
||||||
if (! is_null($country_locale)) {
|
if (! is_null($country_locale)) {
|
||||||
@@ -50,6 +67,17 @@ public function country(Request $request, $country)
|
|||||||
->take(10)
|
->take(10)
|
||||||
->get();
|
->get();
|
||||||
|
|
||||||
|
|
||||||
|
SEOTools::metatags();
|
||||||
|
SEOTools::twitter();
|
||||||
|
SEOTools::opengraph();
|
||||||
|
SEOTools::jsonLd();
|
||||||
|
|
||||||
|
$country_name = get_country_name_by_iso($country_locale->country_iso);
|
||||||
|
|
||||||
|
SEOTools::setTitle("Your {$country_name} Guide to Product Reviews & Top Deals");
|
||||||
|
SEOTools::setDescription("Discover trusted product reviews and unbeatable deals at ProductAlert {$country_name}, your local guide to smart shopping.");
|
||||||
|
|
||||||
return view('front.country', compact('country_locale', 'featured_posts', 'latest_posts')
|
return view('front.country', compact('country_locale', 'featured_posts', 'latest_posts')
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -83,6 +111,20 @@ public function countryCategory(Request $request, $country, $category)
|
|||||||
->distinct()
|
->distinct()
|
||||||
->paginate(15);
|
->paginate(15);
|
||||||
|
|
||||||
|
SEOTools::metatags();
|
||||||
|
SEOTools::twitter();
|
||||||
|
SEOTools::opengraph();
|
||||||
|
SEOTools::jsonLd();
|
||||||
|
|
||||||
|
$country_name = get_country_name_by_iso($country_locale->country_iso);
|
||||||
|
|
||||||
|
SEOTools::setTitle("Top {$category->name} Reviews in {$country_name}");
|
||||||
|
|
||||||
|
$category_name = strtolower($category->name);
|
||||||
|
|
||||||
|
SEOTools::setDescription("Stay updated with the latest {$category_name} product launches in {$country_name}. Find in-depth reviews and exciting deals with ProductAlert, your guide to {$category_name} shopping.");
|
||||||
|
|
||||||
|
|
||||||
return view('front.country_category', compact('country_locale', 'category', 'latest_posts'));
|
return view('front.country_category', compact('country_locale', 'category', 'latest_posts'));
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -101,6 +143,17 @@ public function all(Request $request, $country)
|
|||||||
->distinct()
|
->distinct()
|
||||||
->paginate(15);
|
->paginate(15);
|
||||||
|
|
||||||
|
SEOTools::metatags();
|
||||||
|
SEOTools::twitter();
|
||||||
|
SEOTools::opengraph();
|
||||||
|
SEOTools::jsonLd();
|
||||||
|
|
||||||
|
$country_name = get_country_name_by_iso($country_locale->country_iso);
|
||||||
|
|
||||||
|
SEOTools::setTitle("Find Product Reviews and Best Deals for {$country_name}");
|
||||||
|
|
||||||
|
SEOTools::setDescription("Discover the latest product reviews and unbeatable deals at ProductAlert, your guide to shopping in {$country_name}. Stay on top of fresh product updates.");
|
||||||
|
|
||||||
return view('front.country_all', compact('country_locale', 'latest_posts'));
|
return view('front.country_all', compact('country_locale', 'latest_posts'));
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -110,6 +163,32 @@ public function post(Request $request, $country, $post_slug)
|
|||||||
|
|
||||||
if (! is_null($post)) {
|
if (! is_null($post)) {
|
||||||
|
|
||||||
|
|
||||||
|
SEOMeta::setTitle($post->title);
|
||||||
|
SEOMeta::setDescription($post->excerpt);
|
||||||
|
SEOMeta::addMeta('article:published_time', $post->publish_date, 'property');
|
||||||
|
SEOMeta::addMeta('article:section', $post->post_category->category->name, 'property');
|
||||||
|
|
||||||
|
OpenGraph::setDescription($post->excerpt);
|
||||||
|
OpenGraph::setTitle($post->title);
|
||||||
|
OpenGraph::setUrl(url()->current());
|
||||||
|
OpenGraph::addProperty('type', 'article');
|
||||||
|
OpenGraph::addProperty('locale', $post->post_category->category->country_locale->i18n);
|
||||||
|
OpenGraph::addImage($post->featured_image);
|
||||||
|
|
||||||
|
$jsonld_multi = JsonLdMulti::newJsonLd();
|
||||||
|
$jsonld_multi->setTitle($post->title)
|
||||||
|
->setDescription($post->excerpt)
|
||||||
|
->setType('Article')
|
||||||
|
->addImage($post->featured_image)
|
||||||
|
->addValue('author', $post->author->name)
|
||||||
|
->addValue('datePublished', $post->publish_at)
|
||||||
|
->addValue('dateCreated', $post->publish_at)
|
||||||
|
->addValue('dateModified', $post->updated_at->format('Y-m-d'))
|
||||||
|
->addValue('description', $post->excerpt)
|
||||||
|
->addValue('articleBody', trim(preg_replace('/\s\s+/', ' ', strip_tags($post->html_body))))
|
||||||
|
;
|
||||||
|
|
||||||
return view('front.post', compact('post'));
|
return view('front.post', compact('post'));
|
||||||
}
|
}
|
||||||
abort(404);
|
abort(404);
|
||||||
|
|||||||
@@ -4,13 +4,9 @@
|
|||||||
|
|
||||||
use App\Http\Controllers\Controller;
|
use App\Http\Controllers\Controller;
|
||||||
use Illuminate\Http\Request;
|
use Illuminate\Http\Request;
|
||||||
|
|
||||||
|
|
||||||
use Illuminate\Support\Facades\Storage;
|
use Illuminate\Support\Facades\Storage;
|
||||||
use Intervention\Image\Facades\Image;
|
|
||||||
|
|
||||||
use Illuminate\Support\Str;
|
use Illuminate\Support\Str;
|
||||||
|
use Intervention\Image\Facades\Image;
|
||||||
|
|
||||||
class ImageUploadController extends Controller
|
class ImageUploadController extends Controller
|
||||||
{
|
{
|
||||||
@@ -26,8 +22,8 @@ public function index(Request $request)
|
|||||||
|
|
||||||
// Generate a unique filename for the uploaded file and LQIP version
|
// Generate a unique filename for the uploaded file and LQIP version
|
||||||
$uuid = Str::uuid()->toString();
|
$uuid = Str::uuid()->toString();
|
||||||
$fileName = time() . '_' . $uuid . '.jpg';
|
$fileName = time().'_'.$uuid.'.jpg';
|
||||||
$lqipFileName = time() . '_' . $uuid . '_lqip.jpg';
|
$lqipFileName = time().'_'.$uuid.'_lqip.jpg';
|
||||||
|
|
||||||
// Convert the file to JPEG format using Intervention Image library
|
// Convert the file to JPEG format using Intervention Image library
|
||||||
$image = Image::make($file->getRealPath())->encode('jpg', 100);
|
$image = Image::make($file->getRealPath())->encode('jpg', 100);
|
||||||
@@ -46,8 +42,8 @@ public function index(Request $request)
|
|||||||
$image->encode('jpg', 50);
|
$image->encode('jpg', 50);
|
||||||
|
|
||||||
// Save the processed image to the 'r2' storage driver under the 'uploads' directory
|
// Save the processed image to the 'r2' storage driver under the 'uploads' directory
|
||||||
$filePath = 'uploads/' . $fileName;
|
$filePath = 'uploads/'.$fileName;
|
||||||
$lqipFilePath = 'uploads/' . $lqipFileName;
|
$lqipFilePath = 'uploads/'.$lqipFileName;
|
||||||
Storage::disk('r2')->put($filePath, $image->stream()->detach());
|
Storage::disk('r2')->put($filePath, $image->stream()->detach());
|
||||||
|
|
||||||
// Save the original image to a temporary file and open it again
|
// Save the original image to a temporary file and open it again
|
||||||
@@ -56,7 +52,7 @@ public function index(Request $request)
|
|||||||
$clonedImage = Image::make($tempImagePath);
|
$clonedImage = Image::make($tempImagePath);
|
||||||
|
|
||||||
// Create the LQIP version of the image using a small size while maintaining the aspect ratio
|
// Create the LQIP version of the image using a small size while maintaining the aspect ratio
|
||||||
$lqipImage = $clonedImage->fit(10, 10, function ($constraint) use ($originalWidth, $originalHeight) {
|
$lqipImage = $clonedImage->fit(10, 10, function ($constraint) {
|
||||||
$constraint->aspectRatio();
|
$constraint->aspectRatio();
|
||||||
});
|
});
|
||||||
$lqipImage->encode('jpg', 5);
|
$lqipImage->encode('jpg', 5);
|
||||||
|
|||||||
@@ -40,6 +40,7 @@ class Post extends Model
|
|||||||
'body' => 'json',
|
'body' => 'json',
|
||||||
'comment_count' => 'int',
|
'comment_count' => 'int',
|
||||||
'likes_count' => 'int',
|
'likes_count' => 'int',
|
||||||
|
'featured' => 'bool',
|
||||||
];
|
];
|
||||||
|
|
||||||
protected $fillable = [
|
protected $fillable = [
|
||||||
@@ -54,10 +55,12 @@ class Post extends Model
|
|||||||
'comment_count',
|
'comment_count',
|
||||||
'likes_count',
|
'likes_count',
|
||||||
'status',
|
'status',
|
||||||
|
'featured',
|
||||||
|
'publish_date',
|
||||||
];
|
];
|
||||||
|
|
||||||
protected $appends = [
|
protected $appends = [
|
||||||
'html_body',
|
//'html_body',
|
||||||
];
|
];
|
||||||
|
|
||||||
public function author()
|
public function author()
|
||||||
@@ -78,7 +81,7 @@ public function post_category()
|
|||||||
public function getHtmlBodyAttribute()
|
public function getHtmlBodyAttribute()
|
||||||
{
|
{
|
||||||
if (! is_empty($this->body)) {
|
if (! is_empty($this->body)) {
|
||||||
return LaravelEditorJs::render($this->body);
|
return LaravelEditorJs::render(json_encode($this->body));
|
||||||
}
|
}
|
||||||
|
|
||||||
return '';
|
return '';
|
||||||
|
|||||||
@@ -161,6 +161,7 @@
|
|||||||
*/
|
*/
|
||||||
Barryvdh\Debugbar\ServiceProvider::class,
|
Barryvdh\Debugbar\ServiceProvider::class,
|
||||||
Stevebauman\Location\LocationServiceProvider::class,
|
Stevebauman\Location\LocationServiceProvider::class,
|
||||||
|
Artesaos\SEOTools\Providers\SEOToolsServiceProvider::class,
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Application Service Providers...
|
* Application Service Providers...
|
||||||
@@ -187,6 +188,12 @@
|
|||||||
'aliases' => Facade::defaultAliases()->merge([
|
'aliases' => Facade::defaultAliases()->merge([
|
||||||
// 'Example' => App\Facades\Example::class,
|
// 'Example' => App\Facades\Example::class,
|
||||||
'Debugbar' => Barryvdh\Debugbar\Facades\Debugbar::class,
|
'Debugbar' => Barryvdh\Debugbar\Facades\Debugbar::class,
|
||||||
|
'SEOMeta' => Artesaos\SEOTools\Facades\SEOMeta::class,
|
||||||
|
'OpenGraph' => Artesaos\SEOTools\Facades\OpenGraph::class,
|
||||||
|
'Twitter' => Artesaos\SEOTools\Facades\TwitterCard::class,
|
||||||
|
'JsonLd' => Artesaos\SEOTools\Facades\JsonLd::class,
|
||||||
|
'JsonLdMulti' => Artesaos\SEOTools\Facades\JsonLdMulti::class,
|
||||||
|
'SEO' => Artesaos\SEOTools\Facades\SEOTools::class,
|
||||||
|
|
||||||
])->toArray(),
|
])->toArray(),
|
||||||
|
|
||||||
|
|||||||
@@ -57,18 +57,17 @@
|
|||||||
],
|
],
|
||||||
|
|
||||||
'r2' => [
|
'r2' => [
|
||||||
'driver' => 's3',
|
'driver' => 's3',
|
||||||
'key' => env('CLOUDFLARE_R2_ACCESS_KEY_ID'),
|
'key' => env('CLOUDFLARE_R2_ACCESS_KEY_ID'),
|
||||||
'secret' => env('CLOUDFLARE_R2_SECRET_ACCESS_KEY'),
|
'secret' => env('CLOUDFLARE_R2_SECRET_ACCESS_KEY'),
|
||||||
'region' => env('CLOUDFLARE_R2_REGION'),
|
'region' => env('CLOUDFLARE_R2_REGION'),
|
||||||
'bucket' => env('CLOUDFLARE_R2_BUCKET'),
|
'bucket' => env('CLOUDFLARE_R2_BUCKET'),
|
||||||
'url' => env('CLOUDFLARE_R2_URL'),
|
'url' => env('CLOUDFLARE_R2_URL'),
|
||||||
'visibility' => 'public',
|
'visibility' => 'public',
|
||||||
'endpoint' => env('CLOUDFLARE_R2_ENDPOINT'),
|
'endpoint' => env('CLOUDFLARE_R2_ENDPOINT'),
|
||||||
'use_path_style_endpoint' => env('CLOUDFLARE_R2_USE_PATH_STYLE_ENDPOINT', false),
|
'use_path_style_endpoint' => env('CLOUDFLARE_R2_USE_PATH_STYLE_ENDPOINT', false),
|
||||||
'throw' => true,
|
'throw' => true,
|
||||||
],
|
],
|
||||||
|
|
||||||
|
|
||||||
],
|
],
|
||||||
|
|
||||||
|
|||||||
@@ -9,9 +9,9 @@
|
|||||||
* The default configurations to be used by the meta generator.
|
* The default configurations to be used by the meta generator.
|
||||||
*/
|
*/
|
||||||
'defaults' => [
|
'defaults' => [
|
||||||
'title' => "It's Over 9000!", // set false to total remove
|
'title' => "ProductAlert", // set false to total remove
|
||||||
'titleBefore' => false, // Put defaults.title before page title, like 'It's Over 9000! - Dashboard'
|
'titleBefore' => false, // Put defaults.title before page title, like 'It's Over 9000! - Dashboard'
|
||||||
'description' => 'For those who helped create the Genki Dama', // set false to total remove
|
'description' => 'Find top-rated product reviews at ProductAlert. Discover the latest trends, best brands, and right prices. Your guide to making the best purchase decisions!', // set false to total remove
|
||||||
'separator' => ' - ',
|
'separator' => ' - ',
|
||||||
'keywords' => [],
|
'keywords' => [],
|
||||||
'canonical' => false, // Set to null or 'full' to use Url::full(), set to 'current' to use Url::current(), set false to total remove
|
'canonical' => false, // Set to null or 'full' to use Url::full(), set to 'current' to use Url::current(), set false to total remove
|
||||||
@@ -36,8 +36,8 @@
|
|||||||
* The default configurations to be used by the opengraph generator.
|
* The default configurations to be used by the opengraph generator.
|
||||||
*/
|
*/
|
||||||
'defaults' => [
|
'defaults' => [
|
||||||
'title' => 'Over 9000 Thousand!', // set false to total remove
|
'title' => 'ProductAlert', // set false to total remove
|
||||||
'description' => 'For those who helped create the Genki Dama', // set false to total remove
|
'description' => 'Find top-rated product reviews at ProductAlert. Discover the latest trends, best brands, and right prices. Your guide to making the best purchase decisions!', // set false to total remove
|
||||||
'url' => false, // Set null for using Url::current(), set false to total remove
|
'url' => false, // Set null for using Url::current(), set false to total remove
|
||||||
'type' => false,
|
'type' => false,
|
||||||
'site_name' => false,
|
'site_name' => false,
|
||||||
@@ -58,8 +58,8 @@
|
|||||||
* The default configurations to be used by the json-ld generator.
|
* The default configurations to be used by the json-ld generator.
|
||||||
*/
|
*/
|
||||||
'defaults' => [
|
'defaults' => [
|
||||||
'title' => 'Over 9000 Thousand!', // set false to total remove
|
'title' => 'ProductAlert', // set false to total remove
|
||||||
'description' => 'For those who helped create the Genki Dama', // set false to total remove
|
'description' => 'Find top-rated product reviews at ProductAlert. Discover the latest trends, best brands, and right prices. Your guide to making the best purchase decisions!', // set false to total remove
|
||||||
'url' => false, // Set to null or 'full' to use Url::full(), set to 'current' to use Url::current(), set false to total remove
|
'url' => false, // Set to null or 'full' to use Url::full(), set to 'current' to use Url::current(), set false to total remove
|
||||||
'type' => 'WebPage',
|
'type' => 'WebPage',
|
||||||
'images' => [],
|
'images' => [],
|
||||||
|
|||||||
@@ -0,0 +1,28 @@
|
|||||||
|
<?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('posts', function (Blueprint $table) {
|
||||||
|
$table->date('publish_date')->nullable()->after('author_id');
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Reverse the migrations.
|
||||||
|
*/
|
||||||
|
public function down(): void
|
||||||
|
{
|
||||||
|
Schema::table('posts', function (Blueprint $table) {
|
||||||
|
$table->dropColumn('publish_date');
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
1
public/build/assets/NativeImageBlock-bcbff98b.js
Normal file
1
public/build/assets/NativeImageBlock-bcbff98b.js
Normal file
File diff suppressed because one or more lines are too long
0
public/build/assets/NativeImageBlock-e3b0c442.css
Normal file
0
public/build/assets/NativeImageBlock-e3b0c442.css
Normal file
1
public/build/assets/PostEditor-a6038129.js
Normal file
1
public/build/assets/PostEditor-a6038129.js
Normal file
File diff suppressed because one or more lines are too long
83
public/build/assets/VueEditorJs-6310d292.js
Normal file
83
public/build/assets/VueEditorJs-6310d292.js
Normal file
File diff suppressed because one or more lines are too long
17
public/build/assets/admin-app-0df052b8.js
Normal file
17
public/build/assets/admin-app-0df052b8.js
Normal file
File diff suppressed because one or more lines are too long
1
public/build/assets/admin-app-935fc652.css
Normal file
1
public/build/assets/admin-app-935fc652.css
Normal file
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
32
public/build/assets/bundle-43b5b4d7.js
Normal file
32
public/build/assets/bundle-43b5b4d7.js
Normal file
File diff suppressed because one or more lines are too long
54
public/build/assets/bundle-94bef551.js
Normal file
54
public/build/assets/bundle-94bef551.js
Normal file
File diff suppressed because one or more lines are too long
@@ -1,4 +1,32 @@
|
|||||||
{
|
{
|
||||||
|
"NativeImageBlock.css": {
|
||||||
|
"file": "assets/NativeImageBlock-e3b0c442.css",
|
||||||
|
"src": "NativeImageBlock.css"
|
||||||
|
},
|
||||||
|
"_NativeImageBlock-bcbff98b.js": {
|
||||||
|
"css": [
|
||||||
|
"assets/NativeImageBlock-e3b0c442.css"
|
||||||
|
],
|
||||||
|
"file": "assets/NativeImageBlock-bcbff98b.js",
|
||||||
|
"imports": [
|
||||||
|
"resources/js/admin-app.js"
|
||||||
|
],
|
||||||
|
"isDynamicEntry": true
|
||||||
|
},
|
||||||
|
"_bundle-43b5b4d7.js": {
|
||||||
|
"file": "assets/bundle-43b5b4d7.js",
|
||||||
|
"imports": [
|
||||||
|
"resources/js/admin-app.js"
|
||||||
|
],
|
||||||
|
"isDynamicEntry": true
|
||||||
|
},
|
||||||
|
"_bundle-94bef551.js": {
|
||||||
|
"file": "assets/bundle-94bef551.js",
|
||||||
|
"imports": [
|
||||||
|
"resources/js/admin-app.js"
|
||||||
|
],
|
||||||
|
"isDynamicEntry": true
|
||||||
|
},
|
||||||
"_index-8746c87e.js": {
|
"_index-8746c87e.js": {
|
||||||
"file": "assets/index-8746c87e.js"
|
"file": "assets/index-8746c87e.js"
|
||||||
},
|
},
|
||||||
@@ -10,8 +38,20 @@
|
|||||||
"file": "assets/bootstrap-icons-cfe45b98.woff2",
|
"file": "assets/bootstrap-icons-cfe45b98.woff2",
|
||||||
"src": "node_modules/bootstrap-icons/font/fonts/bootstrap-icons.woff2"
|
"src": "node_modules/bootstrap-icons/font/fonts/bootstrap-icons.woff2"
|
||||||
},
|
},
|
||||||
|
"resources/js/admin-app.css": {
|
||||||
|
"file": "assets/admin-app-935fc652.css",
|
||||||
|
"src": "resources/js/admin-app.css"
|
||||||
|
},
|
||||||
"resources/js/admin-app.js": {
|
"resources/js/admin-app.js": {
|
||||||
"file": "assets/admin-app-ff7516d6.js",
|
"css": [
|
||||||
|
"assets/admin-app-935fc652.css"
|
||||||
|
],
|
||||||
|
"dynamicImports": [
|
||||||
|
"_NativeImageBlock-bcbff98b.js",
|
||||||
|
"resources/js/vue/PostEditor.vue",
|
||||||
|
"resources/js/vue/VueEditorJs.vue"
|
||||||
|
],
|
||||||
|
"file": "assets/admin-app-0df052b8.js",
|
||||||
"imports": [
|
"imports": [
|
||||||
"_index-8746c87e.js"
|
"_index-8746c87e.js"
|
||||||
],
|
],
|
||||||
@@ -26,6 +66,32 @@
|
|||||||
"isEntry": true,
|
"isEntry": true,
|
||||||
"src": "resources/js/front-app.js"
|
"src": "resources/js/front-app.js"
|
||||||
},
|
},
|
||||||
|
"resources/js/vue/PostEditor.vue": {
|
||||||
|
"file": "assets/PostEditor-a6038129.js",
|
||||||
|
"imports": [
|
||||||
|
"resources/js/vue/VueEditorJs.vue",
|
||||||
|
"_NativeImageBlock-bcbff98b.js",
|
||||||
|
"_bundle-43b5b4d7.js",
|
||||||
|
"_bundle-94bef551.js",
|
||||||
|
"resources/js/admin-app.js",
|
||||||
|
"_index-8746c87e.js"
|
||||||
|
],
|
||||||
|
"isDynamicEntry": true,
|
||||||
|
"src": "resources/js/vue/PostEditor.vue"
|
||||||
|
},
|
||||||
|
"resources/js/vue/VueEditorJs.vue": {
|
||||||
|
"dynamicImports": [
|
||||||
|
"_bundle-94bef551.js",
|
||||||
|
"_bundle-43b5b4d7.js"
|
||||||
|
],
|
||||||
|
"file": "assets/VueEditorJs-6310d292.js",
|
||||||
|
"imports": [
|
||||||
|
"resources/js/admin-app.js",
|
||||||
|
"_index-8746c87e.js"
|
||||||
|
],
|
||||||
|
"isDynamicEntry": true,
|
||||||
|
"src": "resources/js/vue/VueEditorJs.vue"
|
||||||
|
},
|
||||||
"resources/sass/admin-app.scss": {
|
"resources/sass/admin-app.scss": {
|
||||||
"file": "assets/admin-app-bade20ce.css",
|
"file": "assets/admin-app-bade20ce.css",
|
||||||
"isEntry": true,
|
"isEntry": true,
|
||||||
|
|||||||
@@ -10,6 +10,7 @@ export const usePostStore = defineStore("postStore", {
|
|||||||
defaultLocaleSlug: "my",
|
defaultLocaleSlug: "my",
|
||||||
countryLocales: [],
|
countryLocales: [],
|
||||||
localeCategories: [],
|
localeCategories: [],
|
||||||
|
authors: [],
|
||||||
},
|
},
|
||||||
}),
|
}),
|
||||||
getters: {
|
getters: {
|
||||||
@@ -22,8 +23,21 @@ export const usePostStore = defineStore("postStore", {
|
|||||||
localeCategories(state) {
|
localeCategories(state) {
|
||||||
return state.data.localeCategories;
|
return state.data.localeCategories;
|
||||||
},
|
},
|
||||||
|
authors(state) {
|
||||||
|
return state.data.authors;
|
||||||
|
},
|
||||||
},
|
},
|
||||||
actions: {
|
actions: {
|
||||||
|
async fetchAuthors() {
|
||||||
|
try {
|
||||||
|
const response = await axios.get(route("api.admin.authors"));
|
||||||
|
console.log(response);
|
||||||
|
this.data.authors = response.data.authors;
|
||||||
|
} catch (error) {
|
||||||
|
// alert(error);
|
||||||
|
console.log(error);
|
||||||
|
}
|
||||||
|
},
|
||||||
async fetchCountryLocales() {
|
async fetchCountryLocales() {
|
||||||
try {
|
try {
|
||||||
const response = await axios.get(route("api.admin.country-locales"));
|
const response = await axios.get(route("api.admin.country-locales"));
|
||||||
|
|||||||
@@ -9,7 +9,7 @@
|
|||||||
<div
|
<div
|
||||||
class="position-absolute w-100 h-100 d-flex justify-content-center text-center"
|
class="position-absolute w-100 h-100 d-flex justify-content-center text-center"
|
||||||
>
|
>
|
||||||
<div v-if="isUploading" class="align-self-center">
|
<div v-if="isUploading || !isLoaded" class="align-self-center">
|
||||||
<div class="spinner-border text-light" role="status">
|
<div class="spinner-border text-light" role="status">
|
||||||
<span class="visually-hidden">Loading...</span>
|
<span class="visually-hidden">Loading...</span>
|
||||||
</div>
|
</div>
|
||||||
@@ -34,16 +34,18 @@
|
|||||||
|
|
||||||
<script>
|
<script>
|
||||||
import axios from "axios";
|
import axios from "axios";
|
||||||
|
import route from "ziggy-js/src/js/index";
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: "NativeImageBlock",
|
name: "NativeImageBlock",
|
||||||
props: {
|
props: {
|
||||||
apiUrl: {
|
inputImage: {
|
||||||
type: String,
|
type: String,
|
||||||
default: "https://productalert.test/api/admin/image/upload",
|
default: null,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
data: () => ({
|
data: () => ({
|
||||||
|
isLoaded: false,
|
||||||
isUploading: false,
|
isUploading: false,
|
||||||
imgSrc: null,
|
imgSrc: null,
|
||||||
placeholderSrc: "https://placekitten.com/g/2100/900",
|
placeholderSrc: "https://placekitten.com/g/2100/900",
|
||||||
@@ -91,7 +93,7 @@ export default {
|
|||||||
formData.append("file", file);
|
formData.append("file", file);
|
||||||
|
|
||||||
axios
|
axios
|
||||||
.post(this.apiUrl, formData, {
|
.post(route("api.admin.upload.cloud.image"), formData, {
|
||||||
headers: {
|
headers: {
|
||||||
"Content-Type": "multipart/form-data",
|
"Content-Type": "multipart/form-data",
|
||||||
},
|
},
|
||||||
@@ -115,9 +117,23 @@ export default {
|
|||||||
this.isUploading = false;
|
this.isUploading = false;
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
setInputImage() {
|
||||||
|
if (this.inputImage != null && this.inputImage?.length > 0) {
|
||||||
|
this.imgSrc = this.inputImage;
|
||||||
|
}
|
||||||
|
this.isLoaded = true;
|
||||||
|
},
|
||||||
},
|
},
|
||||||
mounted() {
|
mounted() {
|
||||||
this.isUploading = false;
|
this.isUploading = false;
|
||||||
|
|
||||||
|
setTimeout(
|
||||||
|
function () {
|
||||||
|
this.setInputImage();
|
||||||
|
this.isLoaded = true;
|
||||||
|
}.bind(this),
|
||||||
|
3000
|
||||||
|
);
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
</script>
|
</script>
|
||||||
|
|||||||
@@ -31,12 +31,13 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<native-image-block
|
<native-image-block
|
||||||
|
ref="imageBlock"
|
||||||
class="mb-3"
|
class="mb-3"
|
||||||
:input-image="post.featured_image"
|
:input-image="post.featured_image"
|
||||||
@saved="imageSaved"
|
@saved="imageSaved"
|
||||||
></native-image-block>
|
></native-image-block>
|
||||||
|
|
||||||
<div class="card">
|
<div v-if="showEditorJs" class="card">
|
||||||
<div class="card-body">
|
<div class="card-body">
|
||||||
<vue-editor-js
|
<vue-editor-js
|
||||||
v-on:saved="editorSaved"
|
v-on:saved="editorSaved"
|
||||||
@@ -62,8 +63,56 @@
|
|||||||
Post Status: {{ item }}
|
Post Status: {{ item }}
|
||||||
</option>
|
</option>
|
||||||
</select>
|
</select>
|
||||||
<button @click="checkAndSave" class="btn btn-primary">
|
<div class="fw-bold">Publish Date</div>
|
||||||
Save as {{ post.status }}
|
<div class="input-icon mb-2">
|
||||||
|
<span class="input-icon-addon"
|
||||||
|
><!-- Download SVG icon from http://tabler-icons.io/i/calendar -->
|
||||||
|
<svg
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
class="icon"
|
||||||
|
width="24"
|
||||||
|
height="24"
|
||||||
|
viewBox="0 0 24 24"
|
||||||
|
stroke-width="2"
|
||||||
|
stroke="currentColor"
|
||||||
|
fill="none"
|
||||||
|
stroke-linecap="round"
|
||||||
|
stroke-linejoin="round"
|
||||||
|
>
|
||||||
|
<path stroke="none" d="M0 0h24v24H0z" fill="none"></path>
|
||||||
|
<path
|
||||||
|
d="M4 7a2 2 0 0 1 2 -2h12a2 2 0 0 1 2 2v12a2 2 0 0 1 -2 2h-12a2 2 0 0 1 -2 -2v-12z"
|
||||||
|
></path>
|
||||||
|
<path d="M16 3v4"></path>
|
||||||
|
<path d="M8 3v4"></path>
|
||||||
|
<path d="M4 11h16"></path>
|
||||||
|
<path d="M11 15h1"></path>
|
||||||
|
<path d="M12 15v3"></path>
|
||||||
|
</svg>
|
||||||
|
</span>
|
||||||
|
<input
|
||||||
|
type="date"
|
||||||
|
v-model="post.publish_date"
|
||||||
|
class="form-control"
|
||||||
|
placeholder="Select a date"
|
||||||
|
id="datepicker-icon-prepend"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<button
|
||||||
|
@click="checkAndSave"
|
||||||
|
class="btn btn-primary"
|
||||||
|
style="height: 50px"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
v-if="isSaving"
|
||||||
|
class="spinner-border"
|
||||||
|
role="status"
|
||||||
|
:disabled="isSaving"
|
||||||
|
:class="isSaving ? 'disabled' : ''"
|
||||||
|
>
|
||||||
|
<span class="visually-hidden">Saving...</span>
|
||||||
|
</div>
|
||||||
|
<span v-else>Save as {{ post.status }}</span>
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
<div class="card mb-2">
|
<div class="card mb-2">
|
||||||
@@ -101,6 +150,36 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
<div class="card mb-2">
|
||||||
|
<div class="card-header fw-bold">Authors</div>
|
||||||
|
<div class="card-body">
|
||||||
|
<div class="py-1" v-for="item in authors" v-bind:key="item.id">
|
||||||
|
<label>
|
||||||
|
<input
|
||||||
|
type="radio"
|
||||||
|
:id="item.id"
|
||||||
|
:value="item.id"
|
||||||
|
v-model="post.author_id"
|
||||||
|
/>
|
||||||
|
{{ item.name }}
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="card mb-2">
|
||||||
|
<div class="card-header fw-bold">Other Settings</div>
|
||||||
|
<div class="card-body">
|
||||||
|
<div class="form-check form-switch">
|
||||||
|
<input
|
||||||
|
v-model="post.featured"
|
||||||
|
class="form-check-input"
|
||||||
|
type="checkbox"
|
||||||
|
role="switch"
|
||||||
|
/>
|
||||||
|
<label class="form-check-label">Feature this Post</label>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -116,16 +195,29 @@ import { mapActions, mapState } from "pinia";
|
|||||||
|
|
||||||
import { usePostStore } from "@/stores/postStore.js";
|
import { usePostStore } from "@/stores/postStore.js";
|
||||||
|
|
||||||
|
import axios from "axios";
|
||||||
|
import route from "ziggy-js/src/js/index";
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
components: { VueEditorJs, List, Header },
|
components: { VueEditorJs, List, Header },
|
||||||
|
props: {
|
||||||
|
postId: {
|
||||||
|
type: Number, // The prop type is Number
|
||||||
|
default: null, // Default value if the prop is not provided
|
||||||
|
},
|
||||||
|
},
|
||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
|
isSaving: false,
|
||||||
|
showEditorJs: false,
|
||||||
post: {
|
post: {
|
||||||
|
id: null,
|
||||||
title: "",
|
title: "",
|
||||||
slug: "",
|
slug: "",
|
||||||
excerpt: "",
|
excerpt: "",
|
||||||
author_id: null,
|
author_id: null,
|
||||||
featured: false,
|
featured: false,
|
||||||
|
publish_date: null,
|
||||||
featured_image: null,
|
featured_image: null,
|
||||||
body: {
|
body: {
|
||||||
time: 1591362820044,
|
time: 1591362820044,
|
||||||
@@ -179,6 +271,7 @@ export default {
|
|||||||
"countryLocales",
|
"countryLocales",
|
||||||
"localeCategories",
|
"localeCategories",
|
||||||
"defaultLocaleSlug",
|
"defaultLocaleSlug",
|
||||||
|
"authors",
|
||||||
]),
|
]),
|
||||||
getPostFullUrl() {
|
getPostFullUrl() {
|
||||||
if (this.post.slug?.length > 0) {
|
if (this.post.slug?.length > 0) {
|
||||||
@@ -200,6 +293,7 @@ export default {
|
|||||||
...mapActions(usePostStore, [
|
...mapActions(usePostStore, [
|
||||||
"fetchCountryLocales",
|
"fetchCountryLocales",
|
||||||
"fetchLocaleCategories",
|
"fetchLocaleCategories",
|
||||||
|
"fetchAuthors",
|
||||||
]),
|
]),
|
||||||
checkAndSave() {
|
checkAndSave() {
|
||||||
let errors = [];
|
let errors = [];
|
||||||
@@ -208,6 +302,10 @@ export default {
|
|||||||
errors.push("post title");
|
errors.push("post title");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (!(this.post.publish_date?.length > 0)) {
|
||||||
|
errors.push("publish date");
|
||||||
|
}
|
||||||
|
|
||||||
if (!(this.post.slug?.length > 0)) {
|
if (!(this.post.slug?.length > 0)) {
|
||||||
errors.push("post slug");
|
errors.push("post slug");
|
||||||
}
|
}
|
||||||
@@ -241,7 +339,36 @@ export default {
|
|||||||
this.savePost();
|
this.savePost();
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
savePost() {},
|
savePost() {
|
||||||
|
this.isSaving = true;
|
||||||
|
|
||||||
|
const formData = new FormData();
|
||||||
|
|
||||||
|
for (const [key, _item] of Object.entries(this.post)) {
|
||||||
|
if (key == "body") {
|
||||||
|
formData.append(key, JSON.stringify(_item));
|
||||||
|
} else {
|
||||||
|
formData.append(key, _item);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
axios
|
||||||
|
.post(route("api.admin.post.upsert"), formData, {
|
||||||
|
headers: {
|
||||||
|
"Content-Type": "application/json",
|
||||||
|
},
|
||||||
|
})
|
||||||
|
.then((response) => {
|
||||||
|
console.warn(response);
|
||||||
|
});
|
||||||
|
|
||||||
|
setTimeout(
|
||||||
|
function () {
|
||||||
|
this.isSaving = false;
|
||||||
|
}.bind(this),
|
||||||
|
1000
|
||||||
|
);
|
||||||
|
},
|
||||||
onInitialized(editor) {},
|
onInitialized(editor) {},
|
||||||
imageSaved(src) {
|
imageSaved(src) {
|
||||||
this.post.featured_image = src;
|
this.post.featured_image = src;
|
||||||
@@ -278,6 +405,34 @@ export default {
|
|||||||
}
|
}
|
||||||
return null;
|
return null;
|
||||||
},
|
},
|
||||||
|
async fetchPostData(id) {
|
||||||
|
const response = await axios.get(route("api.admin.post.get", { id: id }));
|
||||||
|
|
||||||
|
if (response?.data?.post != null) {
|
||||||
|
let tmp = this.post;
|
||||||
|
let post = response.data.post;
|
||||||
|
|
||||||
|
tmp.id = post.id;
|
||||||
|
tmp.title = post.title;
|
||||||
|
tmp.slug = post.slug;
|
||||||
|
tmp.publish_date = post.publish_date;
|
||||||
|
tmp.excerpt = post.excerpt;
|
||||||
|
tmp.author_id = post.author_id;
|
||||||
|
tmp.featured = post.featured;
|
||||||
|
tmp.featured_image = post.featured_image;
|
||||||
|
tmp.body = post.body;
|
||||||
|
tmp.locale_slug = post.post_category.category.country_locale_slug;
|
||||||
|
tmp.locale_id = post.post_category.category.country_locale_id;
|
||||||
|
tmp.status = post.status;
|
||||||
|
tmp.categories = post.post_category.category.id;
|
||||||
|
|
||||||
|
this.post = tmp;
|
||||||
|
|
||||||
|
this.config.data = post.body;
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log(response.data.post);
|
||||||
|
},
|
||||||
slugify: function (title) {
|
slugify: function (title) {
|
||||||
var slug = "";
|
var slug = "";
|
||||||
// Change to lower case
|
// Change to lower case
|
||||||
@@ -300,6 +455,25 @@ export default {
|
|||||||
setTimeout(
|
setTimeout(
|
||||||
function () {
|
function () {
|
||||||
this.fetchLocaleCategories(this.post.locale_slug);
|
this.fetchLocaleCategories(this.post.locale_slug);
|
||||||
|
this.fetchAuthors();
|
||||||
|
|
||||||
|
if (this.postId != null) {
|
||||||
|
this.fetchPostData(this.postId).then(() => {
|
||||||
|
setTimeout(
|
||||||
|
function () {
|
||||||
|
this.showEditorJs = true;
|
||||||
|
}.bind(this),
|
||||||
|
1000
|
||||||
|
);
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
setTimeout(
|
||||||
|
function () {
|
||||||
|
this.showEditorJs = true;
|
||||||
|
}.bind(this),
|
||||||
|
1000
|
||||||
|
);
|
||||||
|
}
|
||||||
}.bind(this),
|
}.bind(this),
|
||||||
100
|
100
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
const Ziggy = {"url":"https:\/\/productalert.test","port":null,"defaults":{},"routes":{"debugbar.openhandler":{"uri":"_debugbar\/open","methods":["GET","HEAD"]},"debugbar.clockwork":{"uri":"_debugbar\/clockwork\/{id}","methods":["GET","HEAD"]},"debugbar.assets.css":{"uri":"_debugbar\/assets\/stylesheets","methods":["GET","HEAD"]},"debugbar.assets.js":{"uri":"_debugbar\/assets\/javascript","methods":["GET","HEAD"]},"debugbar.cache.delete":{"uri":"_debugbar\/cache\/{key}\/{tags?}","methods":["DELETE"]},"sanctum.csrf-cookie":{"uri":"sanctum\/csrf-cookie","methods":["GET","HEAD"]},"ignition.healthCheck":{"uri":"_ignition\/health-check","methods":["GET","HEAD"]},"ignition.executeSolution":{"uri":"_ignition\/execute-solution","methods":["POST"]},"ignition.updateConfig":{"uri":"_ignition\/update-config","methods":["POST"]},"api.auth.login.post":{"uri":"api\/login","methods":["POST"]},"api.auth.logout.post":{"uri":"api\/logout","methods":["POST"]},"api.admin.country-locales":{"uri":"api\/admin\/country-locales","methods":["GET","HEAD"]},"api.admin.categories":{"uri":"api\/admin\/categories\/{country_locale_slug}","methods":["GET","HEAD"]},"api.admin.upload.cloud.image":{"uri":"api\/admin\/image\/upload","methods":["GET","HEAD"]},"login":{"uri":"login","methods":["GET","HEAD"]},"logout":{"uri":"logout","methods":["POST"]},"register":{"uri":"register","methods":["GET","HEAD"]},"password.request":{"uri":"password\/reset","methods":["GET","HEAD"]},"password.email":{"uri":"password\/email","methods":["POST"]},"password.reset":{"uri":"password\/reset\/{token}","methods":["GET","HEAD"]},"password.update":{"uri":"password\/reset","methods":["POST"]},"password.confirm":{"uri":"password\/confirm","methods":["GET","HEAD"]},"dashboard":{"uri":"admin","methods":["GET","HEAD"]},"about":{"uri":"admin\/about","methods":["GET","HEAD"]},"users.index":{"uri":"admin\/users","methods":["GET","HEAD"]},"posts.manage":{"uri":"admin\/posts","methods":["GET","HEAD"]},"posts.manage.edit":{"uri":"admin\/posts\/edit\/{post_id}","methods":["GET","HEAD"]},"posts.manage.new":{"uri":"admin\/posts\/new","methods":["GET","HEAD"]},"profile.show":{"uri":"admin\/profile","methods":["GET","HEAD"]},"profile.update":{"uri":"admin\/profile","methods":["PUT"]},"home":{"uri":"\/","methods":["GET","HEAD"]},"home.country":{"uri":"{country}","methods":["GET","HEAD"]},"home.country.posts":{"uri":"{country}\/posts","methods":["GET","HEAD"]},"home.country.post":{"uri":"{country}\/posts\/{post_slug}","methods":["GET","HEAD"]},"home.country.category":{"uri":"{country}\/{category}","methods":["GET","HEAD"]}}};
|
const Ziggy = {"url":"https:\/\/productalert.test","port":null,"defaults":{},"routes":{"debugbar.openhandler":{"uri":"_debugbar\/open","methods":["GET","HEAD"]},"debugbar.clockwork":{"uri":"_debugbar\/clockwork\/{id}","methods":["GET","HEAD"]},"debugbar.assets.css":{"uri":"_debugbar\/assets\/stylesheets","methods":["GET","HEAD"]},"debugbar.assets.js":{"uri":"_debugbar\/assets\/javascript","methods":["GET","HEAD"]},"debugbar.cache.delete":{"uri":"_debugbar\/cache\/{key}\/{tags?}","methods":["DELETE"]},"sanctum.csrf-cookie":{"uri":"sanctum\/csrf-cookie","methods":["GET","HEAD"]},"ignition.healthCheck":{"uri":"_ignition\/health-check","methods":["GET","HEAD"]},"ignition.executeSolution":{"uri":"_ignition\/execute-solution","methods":["POST"]},"ignition.updateConfig":{"uri":"_ignition\/update-config","methods":["POST"]},"api.auth.login.post":{"uri":"api\/login","methods":["POST"]},"api.auth.logout.post":{"uri":"api\/logout","methods":["POST"]},"api.admin.post.get":{"uri":"api\/admin\/post\/{id}","methods":["GET","HEAD"]},"api.admin.country-locales":{"uri":"api\/admin\/country-locales","methods":["GET","HEAD"]},"api.admin.categories":{"uri":"api\/admin\/categories\/{country_locale_slug}","methods":["GET","HEAD"]},"api.admin.authors":{"uri":"api\/admin\/authors","methods":["GET","HEAD"]},"api.admin.upload.cloud.image":{"uri":"api\/admin\/image\/upload","methods":["POST"]},"api.admin.post.upsert":{"uri":"api\/admin\/admin\/post\/upsert","methods":["POST"]},"login":{"uri":"login","methods":["GET","HEAD"]},"logout":{"uri":"logout","methods":["POST"]},"register":{"uri":"register","methods":["GET","HEAD"]},"password.request":{"uri":"password\/reset","methods":["GET","HEAD"]},"password.email":{"uri":"password\/email","methods":["POST"]},"password.reset":{"uri":"password\/reset\/{token}","methods":["GET","HEAD"]},"password.update":{"uri":"password\/reset","methods":["POST"]},"password.confirm":{"uri":"password\/confirm","methods":["GET","HEAD"]},"dashboard":{"uri":"admin","methods":["GET","HEAD"]},"about":{"uri":"admin\/about","methods":["GET","HEAD"]},"users.index":{"uri":"admin\/users","methods":["GET","HEAD"]},"posts.manage":{"uri":"admin\/posts","methods":["GET","HEAD"]},"posts.manage.edit":{"uri":"admin\/posts\/edit\/{post_id}","methods":["GET","HEAD"]},"posts.manage.new":{"uri":"admin\/posts\/new","methods":["GET","HEAD"]},"profile.show":{"uri":"admin\/profile","methods":["GET","HEAD"]},"profile.update":{"uri":"admin\/profile","methods":["PUT"]},"home":{"uri":"\/","methods":["GET","HEAD"]},"home.country":{"uri":"{country}","methods":["GET","HEAD"]},"home.country.posts":{"uri":"{country}\/posts","methods":["GET","HEAD"]},"home.country.post":{"uri":"{country}\/posts\/{post_slug}","methods":["GET","HEAD"]},"home.country.category":{"uri":"{country}\/{category}","methods":["GET","HEAD"]}}};
|
||||||
|
|
||||||
if (typeof window !== 'undefined' && typeof window.Ziggy !== 'undefined') {
|
if (typeof window !== 'undefined' && typeof window.Ziggy !== 'undefined') {
|
||||||
Object.assign(Ziggy.routes, window.Ziggy.routes);
|
Object.assign(Ziggy.routes, window.Ziggy.routes);
|
||||||
|
|||||||
@@ -29,9 +29,9 @@
|
|||||||
<th>
|
<th>
|
||||||
Image
|
Image
|
||||||
</th>
|
</th>
|
||||||
|
<th>Status</th>
|
||||||
<th>Title</th>
|
<th>Title</th>
|
||||||
<th>{{ __('Created at') }}</th>
|
<th>Datetime</th>
|
||||||
<th>{{ __('Updated in') }}</th>
|
|
||||||
<th>Actions</th>
|
<th>Actions</th>
|
||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
@@ -42,12 +42,33 @@
|
|||||||
<td><img width="80" height="60" src="{{ $post->featured_image }}"
|
<td><img width="80" height="60" src="{{ $post->featured_image }}"
|
||||||
class="img-fluid rounded-2" alt=""></td>
|
class="img-fluid rounded-2" alt=""></td>
|
||||||
<td>
|
<td>
|
||||||
|
@if($post->status === 'publish')
|
||||||
|
<span class="badge bg-success">{{ ucfirst($post->status) }}</span>
|
||||||
|
@elseif($post->status === 'future')
|
||||||
|
<span class="badge bg-primary">{{ ucfirst($post->status) }}</span>
|
||||||
|
@elseif($post->status === 'draft')
|
||||||
|
<span class="badge bg-secondary">{{ ucfirst($post->status) }}</span>
|
||||||
|
@elseif($post->status === 'private')
|
||||||
|
<span class="badge bg-info">{{ ucfirst($post->status) }}</span>
|
||||||
|
@elseif ($post->status == 'trash')
|
||||||
|
<span class="badge bg-danger">{{ ucfirst($post->status) }}</span>
|
||||||
|
@else
|
||||||
|
<span class="badge bg-secondary">{{ ucfirst($post->status) }}</span>
|
||||||
|
@endif
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
@if(!is_empty($post->post_category?->category?->country_locale_slug) && $post->status == 'publish')
|
||||||
<a
|
<a
|
||||||
href="{{ route('home.country.post', ['country' => $post->post_category->category->country_locale_slug, 'post_slug' => $post->slug]) }}">{{ $post->title }}</a>
|
href="{{ route('home.country.post', ['country' => $post->post_category?->category?->country_locale_slug, 'post_slug' => $post->slug]) }}">{{ $post->title }}</a>
|
||||||
|
@else
|
||||||
|
{{ $post->title }}
|
||||||
|
@endif
|
||||||
|
|
||||||
</td>
|
</td>
|
||||||
<td>{{ $post->created_at }}</td>
|
<td>
|
||||||
<td>{{ $post->updated_at->diffForhumans() }}</td>
|
Created at {{ $post->created_at->timezone(session()->get('timezone'))->isoFormat('Do MMMM YYYY, h:mm A') }}<br>
|
||||||
|
Updated {{ $post->updated_at->diffForhumans() }}
|
||||||
|
</td>
|
||||||
<td>
|
<td>
|
||||||
<div><a href="{{ route('posts.manage.edit', ['post_id' => $post->id]) }}"
|
<div><a href="{{ route('posts.manage.edit', ['post_id' => $post->id]) }}"
|
||||||
class="btn">Edit</a></div>
|
class="btn">Edit</a></div>
|
||||||
|
|||||||
@@ -6,19 +6,22 @@
|
|||||||
<div class="page-header d-print-none">
|
<div class="page-header d-print-none">
|
||||||
<h2 class="page-title text-center justify-content-center">
|
<h2 class="page-title text-center justify-content-center">
|
||||||
<div class="align-self-center">
|
<div class="align-self-center">
|
||||||
@if (!is_null($post))
|
@if (!is_null($post))
|
||||||
Edit Post
|
Edit Post
|
||||||
@else
|
@else
|
||||||
New Post
|
New Post
|
||||||
@endif
|
@endif
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
</h2>
|
</h2>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="page-body">
|
<div class="page-body">
|
||||||
<div class="container-xl">
|
<div class="container-xl">
|
||||||
<post-editor></post-editor>
|
@if (!is_null($post))
|
||||||
|
<post-editor :post-id="{{ $post->id }}"></post-editor>
|
||||||
|
@else
|
||||||
|
<post-editor></post-editor>
|
||||||
|
@endif
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@endsection
|
@endsection
|
||||||
|
|||||||
@@ -31,10 +31,10 @@
|
|||||||
<h2 class="h5">{{ $post->excerpt }}</h2>
|
<h2 class="h5">{{ $post->excerpt }}</h2>
|
||||||
</div>
|
</div>
|
||||||
<div class="mb-3">
|
<div class="mb-3">
|
||||||
<img src="{{ $post->featured_image }}" alt="" class="img-fluid rounded-3">
|
<img src="{{ $post->featured_image }}" alt="Photo of {{ $post->name }}" class="img-fluid rounded-3">
|
||||||
</div>
|
</div>
|
||||||
<div class="mb-3">
|
<div class="mb-3">
|
||||||
{{ $post->html_body }}
|
{!! $post->html_body !!}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -5,8 +5,11 @@
|
|||||||
<meta charset="utf-8">
|
<meta charset="utf-8">
|
||||||
<meta http-equiv="X-UA-Compatible" content="IE=edge">
|
<meta http-equiv="X-UA-Compatible" content="IE=edge">
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
|
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
|
||||||
<meta name="description" content="">
|
{!! SEOMeta::generate() !!}
|
||||||
<title>{{ config('app.name', 'Laravel') }}</title>
|
{!! OpenGraph::generate() !!}
|
||||||
|
{!! Twitter::generate() !!}
|
||||||
|
{!! JsonLdMulti::generate() !!}
|
||||||
|
<meta property="fb:app_id" content="{{ config('seotools.fb_app_id') }}" />
|
||||||
|
|
||||||
@vite('resources/sass/front-app.scss')
|
@vite('resources/sass/front-app.scss')
|
||||||
|
|
||||||
|
|||||||
@@ -1,35 +1,34 @@
|
|||||||
<div class="container-fluid border-top">
|
<div class="container-fluid border-top">
|
||||||
<footer class="py-5 container">
|
<footer class="py-5 container">
|
||||||
<div class="row">
|
<div class="row justify-content-center">
|
||||||
<div class="col-6 col-md-2 mb-3">
|
<div class="col-6 col-md-2 mb-3">
|
||||||
<ul class="nav flex-column">
|
<ul class="nav flex-column">
|
||||||
|
|
||||||
@foreach ($categories as $category)
|
@foreach ($categories as $category)
|
||||||
|
@if ($category->id % 2 == 0)
|
||||||
<li class="nav-item mb-2">
|
<li class="nav-item mb-2">
|
||||||
<a class="nav-link p-0 text-body-secondary"
|
<a class="nav-link p-0 text-body-secondary"
|
||||||
href="{{ route('home.country.category', ['country' => $category->country_locale_slug, 'category' => $category->slug]) }}">{{ $category->name }}</a>
|
href="{{ route('home.country.category', ['country' => $category->country_locale_slug, 'category' => $category->slug]) }}">{{ $category->name }}</a>
|
||||||
</li>
|
</li>
|
||||||
@endforeach
|
@endif
|
||||||
|
@endforeach
|
||||||
</ul>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="col-6 col-md-2 mb-3">
|
<div class="col-6 col-md-2 mb-3">
|
||||||
<ul class="nav flex-column">
|
<ul class="nav flex-column">
|
||||||
<li class="nav-item mb-2">
|
@foreach ($categories as $category)
|
||||||
<a href="#" class="nav-link p-0 text-body-secondary">About Us</a>
|
@if ($category->id % 2 == 1)
|
||||||
</li>
|
<li class="nav-item mb-2">
|
||||||
<li class="nav-item mb-2">
|
<a class="nav-link p-0 text-body-secondary"
|
||||||
<a href="#" class="nav-link p-0 text-body-secondary">Contact Us</a>
|
href="{{ route('home.country.category', ['country' => $category->country_locale_slug, 'category' => $category->slug]) }}">{{ $category->name }}</a>
|
||||||
</li>
|
</li>
|
||||||
<li class="nav-item mb-2">
|
@endif
|
||||||
<a href="#" class="nav-link p-0 text-body-secondary">Advertise with us</a>
|
@endforeach
|
||||||
</li>
|
|
||||||
</ul>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
||||||
<div class="col-md-5 offset-md-1 mb-3">
|
<div class="col-md-5 mb-3">
|
||||||
|
|
||||||
|
|
||||||
@if ($country_locales->count() > 1)
|
@if ($country_locales->count() > 1)
|
||||||
@@ -52,15 +51,15 @@
|
|||||||
</div>
|
</div>
|
||||||
@endif
|
@endif
|
||||||
|
|
||||||
<form>
|
{{-- <form>
|
||||||
<h5>Subscribe to our newsletter</h5>
|
<h5>Subscribe to our newsletter</h5>
|
||||||
<p>Monthly digest of what's new and exciting from us.</p>
|
<p>Monthly digest of what's new and exciting from us.</p>
|
||||||
<div class="d-flex flex-column flex-sm-row w-100 gap-2">
|
<div class="d-flex flex-column flex-sm-row w-100 gap-2">
|
||||||
<label for="newsletter1" class="visually-hidden">Email address</label>
|
<label for="newsletter1" class="visually-hidden">Email address</label>
|
||||||
<input id="newsletter1" type="text" class="form-control" placeholder="Email address">
|
<input id="newsletter1" type="disabled" class="form-control disabled" placeholder="Email address">
|
||||||
<button class="btn btn-primary" type="button">Subscribe</button>
|
<button class="btn btn-primary" type="button">Subscribe</button>
|
||||||
</div>
|
</div>
|
||||||
</form>
|
</form> --}}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|||||||
@@ -1,10 +1,10 @@
|
|||||||
<?php
|
<?php
|
||||||
|
|
||||||
use Illuminate\Http\Request;
|
use App\Models\Post;
|
||||||
use Illuminate\Support\Facades\Route;
|
use App\Models\Author;
|
||||||
|
|
||||||
use App\Models\CountryLocale;
|
|
||||||
use App\Models\Category;
|
use App\Models\Category;
|
||||||
|
use App\Models\CountryLocale;
|
||||||
|
use Illuminate\Support\Facades\Route;
|
||||||
|
|
||||||
/*
|
/*
|
||||||
|--------------------------------------------------------------------------
|
|--------------------------------------------------------------------------
|
||||||
@@ -17,30 +17,44 @@
|
|||||||
|
|
|
|
||||||
*/
|
*/
|
||||||
|
|
||||||
Route::post('login', [App\Http\Controllers\Auth\LoginController::class, 'login'])->name('api.auth.login.post');
|
Route::post('login', [App\Http\Controllers\Auth\LoginController::class, 'login'])->name('api.auth.login.post');
|
||||||
|
|
||||||
Route::middleware('auth:sanctum')->post('logout',[App\Http\Controllers\Auth\LoginController::class,'logout'])->name('api.auth.logout.post');
|
Route::middleware('auth:sanctum')->post('logout', [App\Http\Controllers\Auth\LoginController::class, 'logout'])->name('api.auth.logout.post');
|
||||||
|
|
||||||
Route::prefix('admin')->middleware('auth:sanctum')->group(function () {
|
Route::prefix('admin')->middleware('auth:sanctum')->group(function () {
|
||||||
|
|
||||||
Route::get('/country-locales', function() {
|
Route::get('/post/{id}', function ($id) {
|
||||||
|
$post = Post::with('post_category.category')->find($id);
|
||||||
|
|
||||||
|
return response()->json(compact('post'));
|
||||||
|
|
||||||
|
})->name('api.admin.post.get');
|
||||||
|
|
||||||
|
|
||||||
|
Route::get('/country-locales', function () {
|
||||||
$country_locales = CountryLocale::where('enabled', true)->get();
|
$country_locales = CountryLocale::where('enabled', true)->get();
|
||||||
$default_locale_slug = 'my';
|
$default_locale_slug = 'my';
|
||||||
|
|
||||||
return response()->json(compact('country_locales','default_locale_slug'));
|
return response()->json(compact('country_locales', 'default_locale_slug'));
|
||||||
|
|
||||||
})->name('api.admin.country-locales');
|
})->name('api.admin.country-locales');
|
||||||
|
|
||||||
Route::get('/categories/{country_locale_slug}', function($country_locale_slug) {
|
Route::get('/categories/{country_locale_slug}', function ($country_locale_slug) {
|
||||||
$categories = Category::where('enabled', true)->where('country_locale_slug', $country_locale_slug)->get();
|
$categories = Category::where('enabled', true)->where('country_locale_slug', $country_locale_slug)->get();
|
||||||
|
|
||||||
return response()->json(compact('categories'));
|
return response()->json(compact('categories'));
|
||||||
|
|
||||||
})->name('api.admin.categories');
|
})->name('api.admin.categories');
|
||||||
|
|
||||||
|
Route::get('/authors', function () {
|
||||||
|
$authors = Author::where('enabled', true)->get();
|
||||||
|
|
||||||
|
return response()->json(compact('authors'));
|
||||||
|
|
||||||
|
})->name('api.admin.authors');
|
||||||
|
|
||||||
|
Route::post('image/upload', [App\Http\Controllers\Services\ImageUploadController::class, 'index'])->name('api.admin.upload.cloud.image');
|
||||||
|
|
||||||
|
Route::post('admin/post/upsert', [App\Http\Controllers\Admin\PostController::class, 'postUpsert'])->name('api.admin.post.upsert');
|
||||||
|
|
||||||
});
|
});
|
||||||
|
|
||||||
Route::post('admin/image/upload', [App\Http\Controllers\Services\ImageUploadController::class, 'index'])->name('api.admin.upload.cloud.image');
|
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user