diff --git a/.env.example b/.env.example
index ae84bd3..bd251a7 100644
--- a/.env.example
+++ b/.env.example
@@ -57,6 +57,9 @@ VITE_PUSHER_PORT="${PUSHER_PORT}"
VITE_PUSHER_SCHEME="${PUSHER_SCHEME}"
VITE_PUSHER_APP_CLUSTER="${PUSHER_APP_CLUSTER}"
+GOOGLE_CLIENT_ID=
+GOOGLE_CLIENT_SECRET=
+
HTTPS_AWARE=false
DS_APP_HOST=10.25.10.1
diff --git a/README.md b/README.md
index 48398a1..652dfe5 100644
--- a/README.md
+++ b/README.md
@@ -34,10 +34,20 @@ password : password
npm run build
```
-## Rsync
+## Other
+
+### v1
```bash
rsync -arP -e 'ssh -p 224' --exclude=node_modules --exclude=.git --exclude=.env --exclude=storage --exclude=public/hot . arm@ajikamaludin.id:/home/arm/projects/voucher
rsync -arP -e 'ssh -p 224' --exclude=node_modules --exclude=database/database.sqlite --exclude=.git --exclude=.env --exclude=storage --exclude=public/hot . arm@ajikamaludin.id:/home/arm/projects/voucher
```
+
+### v2
+
+```bash
+rsync -arP -e 'ssh -p 225' --exclude=node_modules --exclude=database/database.sqlite --exclude=.git --exclude=.env --exclude=public/hot . arm@ajikamaludin.id:/home/arm/projects/www/voucher
+
+ssh -p 225 arm@ajikamaludin.id -C docker exec php82 php /var/www/voucher/artisan migrate:refresh --seed
+```
diff --git a/app/Http/Controllers/Customer/AuthController.php b/app/Http/Controllers/Customer/AuthController.php
index a2a16a3..71c3fc0 100644
--- a/app/Http/Controllers/Customer/AuthController.php
+++ b/app/Http/Controllers/Customer/AuthController.php
@@ -62,6 +62,7 @@ class AuthController extends Controller
->setConfig($this->config)
->user();
} catch (\Exception $e) {
+ info('auth google error', ['exception' => $e]);
return redirect()->route('customer.login')
->with('message', ['type' => 'error', 'message' => 'Google authentication fail, please try again']);
}
@@ -73,11 +74,13 @@ class AuthController extends Controller
'fullname' => $user->name,
'name' => $user->nickname,
'email' => $user->email,
- 'username' => Str::slug($user->name.'_'.Str::random(5), '_'),
+ 'username' => Str::slug($user->name . '_' . Str::random(5), '_'),
'google_id' => $user->id,
'google_oauth_response' => json_encode($user),
]);
DB::commit();
+ } else {
+ $customer->update(['google_oauth_response' => json_encode($user)]);
}
Auth::guard('customer')->loginUsingId($customer->id);
@@ -98,7 +101,7 @@ class AuthController extends Controller
'fullname' => 'required|string',
'name' => 'required|string',
'address' => 'required|string',
- 'phone' => 'required|numeric|regex:/^([0-9\s\-\+\(\)]*)$/|min:9|max:16',
+ 'phone' => 'required|regex:/^([0-9\s\-\+\(\)]*)$/|min:9|max:16',
'username' => 'required|string|min:5|alpha_dash|unique:customers,username',
'password' => 'required|string|min:8|confirmed',
'referral_code' => 'nullable|exists:customers,referral_code',
@@ -126,7 +129,7 @@ class AuthController extends Controller
$bonuspoin = Setting::getByKey('AFFILATE_poin_AMOUNT');
$poin = $refferal->poins()->create([
'debit' => $bonuspoin,
- 'description' => 'Bonus Refferal #'.Str::random(5),
+ 'description' => 'Bonus Refferal #' . Str::random(5),
]);
$poin->update_customer_balance();
diff --git a/app/Http/Controllers/Customer/HomeController.php b/app/Http/Controllers/Customer/HomeController.php
index d3d9cde..c06ae18 100644
--- a/app/Http/Controllers/Customer/HomeController.php
+++ b/app/Http/Controllers/Customer/HomeController.php
@@ -4,6 +4,8 @@ namespace App\Http\Controllers\Customer;
use App\Http\Controllers\Controller;
use App\Models\Banner;
+use App\Models\Customer;
+use App\Models\CustomerLocationFavorite;
use App\Models\Info;
use App\Models\Location;
use App\Models\Notification;
@@ -12,25 +14,75 @@ use Illuminate\Http\Request;
class HomeController extends Controller
{
+ const LIMIT = 2;
+
public function index(Request $request)
{
+ if ($request->direct != '') {
+ $customer = Customer::find(auth()->id());
+ if ($request->location_ids == '' && $customer->locationFavorites()->count() > 0) {
+ return redirect()->route('customer.home.favorite');
+ }
+ }
+
$infos = Info::where('is_publish', 1)->orderBy('updated_at', 'desc')->get();
$banners = Banner::orderBy('updated_at', 'desc')->get();
- $locations = Location::orderBy('updated_at', 'desc')->get();
- $vouchers = Voucher::with(['location'])
+ $locations = Location::orderBy('name', 'asc')->get();
+
+ $slocations = [];
+
+ $vouchers = Voucher::with(['locationProfile.location'])
->where('is_sold', Voucher::UNSOLD)
- ->orderBy('updated_at', 'desc');
+ ->orderBy('updated_at', 'desc')
+ ->groupBy('location_profile_id');
+
+ if ($request->location_ids != '') {
+ $vouchers->whereHas('locationProfile', function ($q) use ($request) {
+ return $q->whereIn('location_id', $request->location_ids);
+ });
+
+ $slocations = Location::whereIn('id', $request->location_ids)->get();
- if ($request->location_id != '') {
- $vouchers->where('location_id', $request->location_id);
+ $vouchers = tap($vouchers->paginate(self::LIMIT))->setHidden(['username', 'password']);
+ }
+
+ if (auth()->guard('customer')->guest() && $request->location_ids != '') {
+ $vouchers = tap($vouchers->paginate(self::LIMIT))->setHidden(['username', 'password']);
}
return inertia('Index/Index', [
'infos' => $infos,
'banners' => $banners,
'locations' => $locations,
- 'vouchers' => tap($vouchers->paginate(10))->setHidden(['username', 'password']),
- '_location_id' => $request->location_id ?? '',
+ 'vouchers' => $vouchers,
+ '_slocations' => $slocations,
+ '_status' => 0
+ ]);
+ }
+
+ public function favorite()
+ {
+ $infos = Info::where('is_publish', 1)->orderBy('updated_at', 'desc')->get();
+ $banners = Banner::orderBy('updated_at', 'desc')->get();
+ $locations = Location::orderBy('name', 'asc')->get();
+
+ $vouchers = Voucher::with(['locationProfile.location'])
+ ->where('is_sold', Voucher::UNSOLD)
+ ->orderBy('updated_at', 'desc')
+ ->groupBy('location_profile_id');
+
+ $customer = Customer::find(auth()->id());
+ $vouchers->whereHas('locationProfile', function ($q) use ($customer) {
+ return $q->whereIn('location_id', $customer->locationFavorites()->pluck('id')->toArray());
+ });
+
+ return inertia('Index/Index', [
+ 'infos' => $infos,
+ 'banners' => $banners,
+ 'locations' => $locations,
+ 'vouchers' => tap($vouchers->paginate(self::LIMIT))->setHidden(['username', 'password']),
+ '_flocations' => $customer->locationFavorites,
+ '_status' => 1
]);
}
@@ -41,6 +93,13 @@ class HomeController extends Controller
]);
}
+ public function addFavorite(Location $location)
+ {
+ $customer = Customer::find(auth()->id());
+
+ $customer->locationFavorites()->toggle([$location->id]);
+ }
+
public function notification()
{
Notification::where('entity_id', auth()->id())->where('is_read', Notification::UNREAD)->update(['is_read' => Notification::READ]);
diff --git a/app/Http/Middleware/HandleInertiaCustomerRequests.php b/app/Http/Middleware/HandleInertiaCustomerRequests.php
index 690e3d4..6c54c1c 100644
--- a/app/Http/Middleware/HandleInertiaCustomerRequests.php
+++ b/app/Http/Middleware/HandleInertiaCustomerRequests.php
@@ -48,7 +48,7 @@ class HandleInertiaCustomerRequests extends Middleware
'app_name' => env('APP_NAME', 'App Name'),
'setting' => Setting::getSettings(),
'auth' => [
- 'user' => auth('customer')->user()?->load(['level', 'paylater']),
+ 'user' => auth('customer')->user()?->load(['level', 'paylater', 'locationFavorites']),
],
'flash' => [
'message' => fn () => $request->session()->get('message') ?? ['type' => null, 'message' => null],
diff --git a/app/Models/Customer.php b/app/Models/Customer.php
index 9163cc2..083be54 100644
--- a/app/Models/Customer.php
+++ b/app/Models/Customer.php
@@ -6,6 +6,7 @@ use App\Models\Traits\UserTrackable;
use Illuminate\Database\Eloquent\Casts\Attribute;
use Illuminate\Database\Eloquent\Concerns\HasUlids;
use Illuminate\Database\Eloquent\Factories\HasFactory;
+use Illuminate\Database\Eloquent\Relations\HasManyThrough;
use Illuminate\Database\Eloquent\SoftDeletes;
use Illuminate\Foundation\Auth\User as Authenticatable;
use Illuminate\Support\Str;
@@ -126,7 +127,7 @@ class Customer extends Authenticatable
return ' - ';
}
- return '+62'.$this->phone;
+ return '+62' . $this->phone;
});
}
@@ -234,7 +235,7 @@ class Customer extends Authenticatable
$paylater = $this->paylaterHistories()->create([
'credit' => $cut,
- 'description' => $deposit->description.' (Pengembalian)',
+ 'description' => $deposit->description . ' (Pengembalian)',
]);
$paylater->update_customer_paylater();
@@ -245,4 +246,9 @@ class Customer extends Authenticatable
$deposit->update_customer_balance();
}
}
+
+ public function locationFavorites()
+ {
+ return $this->belongsToMany(Location::class, CustomerLocationFavorite::class);
+ }
}
diff --git a/app/Models/CustomerLocationFavorite.php b/app/Models/CustomerLocationFavorite.php
index fd57b6a..20f6b35 100644
--- a/app/Models/CustomerLocationFavorite.php
+++ b/app/Models/CustomerLocationFavorite.php
@@ -2,10 +2,8 @@
namespace App\Models;
-class CustomerLocationFavorite extends Model
+use Illuminate\Database\Eloquent\Relations\Pivot;
+
+class CustomerLocationFavorite extends Pivot
{
- protected $fillable = [
- 'customer_id',
- 'location_id',
- ];
}
diff --git a/app/Models/LocationProfile.php b/app/Models/LocationProfile.php
index 1d2414a..06a8bf3 100644
--- a/app/Models/LocationProfile.php
+++ b/app/Models/LocationProfile.php
@@ -78,7 +78,7 @@ class LocationProfile extends Model
public function diplayExpired(): Attribute
{
return Attribute::make(get: function () {
- return $this->expired.' '.$this->expired_unit;
+ return $this->expired . ' ' . $this->expired_unit;
});
}
diff --git a/app/Models/Voucher.php b/app/Models/Voucher.php
index 7283c70..7801412 100644
--- a/app/Models/Voucher.php
+++ b/app/Models/Voucher.php
@@ -3,6 +3,7 @@
namespace App\Models;
use Illuminate\Database\Eloquent\Casts\Attribute;
+use Illuminate\Support\Facades\Auth;
class Voucher extends Model
{
@@ -25,10 +26,24 @@ class Voucher extends Model
protected $appends = [
'validate_price',
'validate_display_price',
+ 'discount',
'status',
'created_at_formated'
];
+ private static $instance = [];
+
+ private static function getInstance()
+ {
+ if (count(self::$instance) == 0) {
+ self::$instance = [
+ 'customer' => Customer::find(auth()->id())
+ ];
+ }
+
+ return self::$instance;
+ }
+
public function locationProfile()
{
return $this->belongsTo(LocationProfile::class, 'location_profile_id');
@@ -37,14 +52,48 @@ class Voucher extends Model
public function validatePrice(): Attribute
{
return Attribute::make(get: function () {
- return '';
+ if ($this->locationProfile->prices->count() > 0) {
+ $price = $this->locationProfile->prices;
+ if (auth()->guard('customer')->check()) {
+ $customer = self::getInstance()['customer'];
+ return $price->where('customer_level_id', $customer->customer_level_id)
+ ->value('price');
+ }
+ return $price->max('price');
+ }
+ return $this->locationProfile->price;
});
}
public function validateDisplayPrice(): Attribute
{
return Attribute::make(get: function () {
- return '';
+ if ($this->locationProfile->prices->count() > 0) {
+ $price = $this->locationProfile->prices;
+ if (auth()->guard('customer')->check()) {
+ $customer = self::getInstance()['customer'];
+ return $price->where('customer_level_id', $customer->customer_level_id)
+ ->value('display_price');
+ }
+ return $price->max('display_price');
+ }
+ return $this->locationProfile->display_price;
+ });
+ }
+
+ public function discount(): Attribute
+ {
+ return Attribute::make(get: function () {
+ if ($this->locationProfile->prices->count() > 0) {
+ $price = $this->locationProfile->prices;
+ if (auth()->guard('customer')->check()) {
+ $customer = self::getInstance()['customer'];
+ return $price->where('customer_level_id', $customer->customer_level_id)
+ ->value('discount');
+ }
+ return $price->min('discount');
+ }
+ return $this->locationProfile->discount;
});
}
diff --git a/database/migrations/2023_06_16_071025_create_customer_location_favorites_table.php b/database/migrations/2023_06_16_071025_create_customer_location_favorites_table.php
index fb057cc..d35ad7e 100644
--- a/database/migrations/2023_06_16_071025_create_customer_location_favorites_table.php
+++ b/database/migrations/2023_06_16_071025_create_customer_location_favorites_table.php
@@ -11,17 +11,12 @@ return new class extends Migration
*/
public function up(): void
{
- Schema::create('customer_location_favorites', function (Blueprint $table) {
- $table->ulid('id')->primary();
-
+ Schema::create('customer_location_favorite', function (Blueprint $table) {
$table->ulid('location_id')->nullable();
$table->ulid('customer_id')->nullable();
$table->timestamps();
$table->softDeletes();
- $table->ulid('created_by')->nullable();
- $table->ulid('updated_by')->nullable();
- $table->ulid('deleted_by')->nullable();
});
}
@@ -30,6 +25,6 @@ return new class extends Migration
*/
public function down(): void
{
- Schema::dropIfExists('customer_location_favorites');
+ Schema::dropIfExists('customer_location_favorite');
}
};
diff --git a/resources/js/Components/Modal.jsx b/resources/js/Components/Modal.jsx
index 2c0b159..86f0622 100644
--- a/resources/js/Components/Modal.jsx
+++ b/resources/js/Components/Modal.jsx
@@ -1,23 +1,37 @@
-import React from "react";
-import { HiX } from "react-icons/hi";
+import React from 'react'
+import { HiX } from 'react-icons/hi'
-
-export default function Modal({ isOpen, toggle = () => {}, children, title = "", maxW = '2' }) {
+export default function Modal({
+ isOpen,
+ toggle = () => {},
+ children,
+ title = '',
+ maxW = '2',
+}) {
return (
-
+
-
{ title }
-
-
- {children}
-
+
{children}
)
-}
\ No newline at end of file
+}
diff --git a/resources/js/Customer/Components/FormLocation.jsx b/resources/js/Customer/Components/FormLocation.jsx
index 8df9d99..8a17d57 100644
--- a/resources/js/Customer/Components/FormLocation.jsx
+++ b/resources/js/Customer/Components/FormLocation.jsx
@@ -1,35 +1,29 @@
-import React from 'react'
-import { isEmpty } from 'lodash'
-import { HiFilter } from 'react-icons/hi'
+import React, { forwardRef } from 'react'
+import { HiOutlineFilter, HiOutlineSearch } from 'react-icons/hi'
-export default function FormLocation({
- type,
- name,
- onChange,
- value,
- label,
- autoComplete,
- autoFocus,
- placeholder,
- disabled,
- readOnly,
- onKeyDownCapture,
- max,
- min,
-}) {
- return (
- <>
-
+const FormLocation = forwardRef(
+ (
+ {
+ name,
+ onChange,
+ value,
+ autoComplete,
+ autoFocus,
+ placeholder,
+ disabled,
+ readOnly,
+ onKeyDownCapture,
+ max,
+ min,
+ },
+ ref
+ ) => {
+ return (
- >
- )
-}
+ )
+ }
+)
+
+export default FormLocation
diff --git a/resources/js/Customer/Components/Modal.jsx b/resources/js/Customer/Components/Modal.jsx
new file mode 100644
index 0000000..dfde8b7
--- /dev/null
+++ b/resources/js/Customer/Components/Modal.jsx
@@ -0,0 +1,15 @@
+export default function Modal({ isOpen, children }) {
+ return (
+
+ )
+}
diff --git a/resources/js/Customer/Index/Index.jsx b/resources/js/Customer/Index/Index.jsx
index 1ccd0af..f47a856 100644
--- a/resources/js/Customer/Index/Index.jsx
+++ b/resources/js/Customer/Index/Index.jsx
@@ -1,12 +1,12 @@
-import React, { useState } from 'react'
+import React from 'react'
import { Head, router, usePage } from '@inertiajs/react'
+import { HiOutlineBell } from 'react-icons/hi2'
+import { handleBanner, ALL, FAVORITE } from './utils'
import CustomerLayout from '@/Layouts/CustomerLayout'
-import { HiOutlineBell } from 'react-icons/hi2'
-import UserBanner from './UserBanner'
-import VoucherCard from './VoucherCard'
-import FormLocation from '../Components/FormLocation'
-import { HiXCircle } from 'react-icons/hi'
+import UserBanner from './Partials/UserBanner'
+import AllVoucher from './IndexPartials/AllVoucher'
+import FavoriteVoucher from './IndexPartials/FavoriteVoucher'
const GuestBanner = () => {
const {
@@ -37,63 +37,32 @@ export default function Index(props) {
auth: { user },
infos,
banners,
- locations,
- vouchers: { data, next_page_url },
- _location_id,
+ _status,
} = props
- const [locId, setLocId] = useState(_location_id)
- const [v, setV] = useState(data)
-
- const handleBanner = (banner) => {
- router.get(route('home.banner', banner))
- }
-
- const handleSelectLoc = (loc) => {
- if (loc.id === locId) {
- setLocId('')
- fetch('')
- return
+ const isStatus = (s) => {
+ if (s === _status) {
+ return 'px-2 py-1 rounded-2xl text-white bg-blue-600 border border-blue-800'
}
- setLocId(loc.id)
- fetch(loc.id)
+ return 'px-2 py-1 rounded-2xl bg-blue-100 border border-blue-200'
}
- const handleNextPage = () => {
- router.get(
- next_page_url,
- {
- location_id: locId,
- },
- {
- replace: true,
- preserveState: true,
- only: ['vouchers'],
- onSuccess: (res) => {
- setV(v.concat(res.props.vouchers.data))
- },
- }
- )
+ const handleFavorite = () => {
+ if (user === null) {
+ router.visit(route('customer.login'))
+ }
+ router.visit(route('customer.home.favorite'))
}
- const fetch = (locId) => {
- router.get(
- route(route().current()),
- { location_id: locId },
- {
- replace: true,
- preserveState: true,
- onSuccess: (res) => {
- setV(res.props.vouchers.data)
- },
- }
- )
+ const handleAll = () => {
+ router.visit(route('home.index'))
}
return (
+ {/* guest or user banner */}
{user !== null ?
:
}
{/* banner */}
@@ -127,59 +96,21 @@ export default function Index(props) {
-
-
-
{/* chips */}
-
+
Semua
- Favorite
+ Favorit
-
-
- {/*
- {locations.map((location) => (
-
handleSelectLoc(location)}
- key={location.id}
- className={`px-2 py-1 rounded-2xl ${
- location.id === locId
- ? 'text-white bg-blue-600 border border-blue-800'
- : 'bg-blue-100 border border-blue-200'
- }`}
- >
- {location.name}
-
- ))}
-
*/}
-
- {/* voucher */}
-
- {v.map((voucher) => (
-
- ))}
- {next_page_url !== null && (
-
- Load more
-
- )}
+ {_status === ALL &&
}
+ {_status === FAVORITE &&
}
)
diff --git a/resources/js/Customer/Index/IndexPartials/AllVoucher.jsx b/resources/js/Customer/Index/IndexPartials/AllVoucher.jsx
new file mode 100644
index 0000000..7e70a23
--- /dev/null
+++ b/resources/js/Customer/Index/IndexPartials/AllVoucher.jsx
@@ -0,0 +1,148 @@
+import React, { useState } from 'react'
+import { router, usePage } from '@inertiajs/react'
+import { HiXMark } from 'react-icons/hi2'
+
+import { useModalState } from '@/hooks'
+import VoucherCard from '../Partials/VoucherCard'
+import FormLocation from '../../Components/FormLocation'
+import LocationModal from '../Partials/LocationModal'
+
+const EmptyLocation = () => {
+ return (
+
+
Pilih lokasi
+
+ pilih lokasi untuk dapat menampilkan voucher tersedia
+
+
+ )
+}
+
+const EmptyVoucher = () => {
+ return (
+
+
Voucher belum tersedia
+
+ sepertinya voucher di lokasimu sedang tidak tersedia
+
+
+ )
+}
+
+export default function AllVoucher() {
+ const {
+ props: {
+ locations,
+ vouchers: { data, next_page_url },
+ _slocations,
+ },
+ } = usePage()
+
+ const locationModal = useModalState()
+
+ const nextPageUrl = next_page_url === undefined ? null : next_page_url
+ const [items, setItems] = useState(data === undefined ? [] : data)
+ const [sLocations, setSLocations] = useState(_slocations)
+
+ const handleAddLocation = (location) => {
+ const isExists = sLocations.find((l) => l.id === location.id)
+ if (!isExists) {
+ const locations = [location].concat(...sLocations)
+ setSLocations(locations)
+ fetch(locations)
+ }
+ }
+
+ const handleRemoveLocation = (index) => {
+ const locations = sLocations.filter((_, i) => i !== index)
+ setSLocations(locations)
+ fetch(locations)
+ }
+
+ const handleNextPage = () => {
+ let location_ids = sLocations.map((l) => l.id)
+
+ router.get(
+ nextPageUrl,
+ { location_ids: location_ids },
+ {
+ replace: true,
+ preserveState: true,
+ only: ['vouchers'],
+ onSuccess: (res) => {
+ if (res.props.vouchers.data !== undefined) {
+ setItems(items.concat(res.props.vouchers.data))
+ }
+ },
+ }
+ )
+ }
+
+ const fetch = (locations) => {
+ let location_ids = locations.map((l) => l.id)
+
+ router.get(
+ route(route().current()),
+ { location_ids: location_ids },
+ {
+ replace: true,
+ preserveState: true,
+ onSuccess: (res) => {
+ if (res.props.vouchers.data !== undefined) {
+ setItems(res.props.vouchers.data)
+ return
+ }
+ setItems([])
+ },
+ }
+ )
+ }
+
+ return (
+ <>
+
+
+
+
+ {sLocations.map((location, index) => (
+
handleRemoveLocation(index)}
+ >
+
{location.name}
+
+
+
+
+ ))}
+
+ {items.length <= 0 && sLocations.length <= 0 &&
}
+
+ {/* voucher */}
+
+ {items.map((voucher) => (
+
+ ))}
+ {nextPageUrl !== null && (
+
+ Load more
+
+ )}
+
+ {items.length <= 0 && sLocations.length > 0 &&
}
+
+
+ >
+ )
+}
diff --git a/resources/js/Customer/Index/IndexPartials/FavoriteVoucher.jsx b/resources/js/Customer/Index/IndexPartials/FavoriteVoucher.jsx
new file mode 100644
index 0000000..434851c
--- /dev/null
+++ b/resources/js/Customer/Index/IndexPartials/FavoriteVoucher.jsx
@@ -0,0 +1,108 @@
+import React, { useEffect, useState } from 'react'
+import { router, usePage } from '@inertiajs/react'
+import { HiOutlineStar } from 'react-icons/hi2'
+
+import VoucherCard from '../Partials/VoucherCard'
+
+const EmptyFavorite = () => {
+ return (
+
+
Favorite kosong
+
+ pilih lokasi favorite mu ya, cek bintangnya
+
+
+ )
+}
+
+const EmptyVoucher = () => {
+ return (
+
+
Voucher belum tersedia
+
+ sepertinya voucher di lokasimu sedang tidak tersedia
+
+
+ )
+}
+
+export default function FavoriteVoucher() {
+ const {
+ props: {
+ vouchers: { data, next_page_url },
+ _flocations,
+ },
+ } = usePage()
+
+ const nextPageUrl = next_page_url === undefined ? null : next_page_url
+ const [items, setItems] = useState(data === undefined ? [] : data)
+
+ const handleRemoveLocation = (location) => {
+ router.post(
+ route('customer.location.favorite', location),
+ {},
+ {
+ onSuccess: () => {
+ router.visit(route(route().current()))
+ },
+ }
+ )
+ }
+
+ const handleNextPage = () => {
+ router.get(
+ nextPageUrl,
+ {},
+ {
+ replace: true,
+ preserveState: true,
+ only: ['vouchers'],
+ onSuccess: (res) => {
+ if (res.props.vouchers.data !== undefined) {
+ setItems(items.concat(res.props.vouchers.data))
+ }
+ },
+ }
+ )
+ }
+
+ useEffect(() => {
+ setItems(data)
+ }, [_flocations])
+
+ return (
+ <>
+
+ {_flocations.map((location) => (
+
handleRemoveLocation(location)}
+ >
+
{location.name}
+
+
+
+
+ ))}
+
+ {_flocations.length <= 0 &&
}
+
+ {/* voucher */}
+
+ {items.map((voucher) => (
+
+ ))}
+ {nextPageUrl !== null && (
+
+ Load more
+
+ )}
+
+ {items.length <= 0 &&
}
+ >
+ )
+}
diff --git a/resources/js/Customer/Index/BalanceBanner.jsx b/resources/js/Customer/Index/Partials/BalanceBanner.jsx
similarity index 100%
rename from resources/js/Customer/Index/BalanceBanner.jsx
rename to resources/js/Customer/Index/Partials/BalanceBanner.jsx
diff --git a/resources/js/Customer/Index/Partials/LocationModal.jsx b/resources/js/Customer/Index/Partials/LocationModal.jsx
new file mode 100644
index 0000000..d384518
--- /dev/null
+++ b/resources/js/Customer/Index/Partials/LocationModal.jsx
@@ -0,0 +1,98 @@
+import { useEffect, useState } from 'react'
+import { router, usePage } from '@inertiajs/react'
+import { HiArrowLeft, HiOutlineStar } from 'react-icons/hi2'
+
+import { useAutoFocus } from '@/hooks'
+import { isFavorite } from '../utils'
+import FormLocation from '../../Components/FormLocation'
+import Modal from '../../Components/Modal'
+
+export default function LocationModal(props) {
+ const {
+ props: {
+ auth: { user },
+ },
+ } = usePage()
+ const { state, locations, onItemSelected } = props
+
+ const [search, setSearch] = useState('')
+ const locationFocus = useAutoFocus()
+ const [filter_locations, setFilterLocations] = useState(locations)
+
+ const handleOnFilter = (e) => {
+ setSearch(e.target.value)
+ if (e.target.value === '') {
+ setFilterLocations(locations)
+ return
+ }
+ setFilterLocations(
+ filter_locations.filter((location) => {
+ let name = location.name.toLowerCase()
+ let search = e.target.value.toLowerCase()
+ return name.includes(search)
+ })
+ )
+ }
+
+ const handleItemSelected = (location) => {
+ onItemSelected(location)
+ state.toggle()
+ }
+
+ const handleFavorite = (location) => {
+ router.post(route('customer.location.favorite', location))
+ }
+
+ useEffect(() => {
+ if (state.isOpen === true) {
+ locationFocus.current.focus()
+ }
+ }, [state])
+
+ return (
+
+
+
+ {filter_locations.map((location) => (
+
+
handleItemSelected(location)}
+ className="flex-1 px-3 py-3"
+ >
+ {location.name}
+
+
handleFavorite(location)}
+ >
+
+
+
+ ))}
+
+
+ )
+}
diff --git a/resources/js/Customer/Index/UserBanner.jsx b/resources/js/Customer/Index/Partials/UserBanner.jsx
similarity index 100%
rename from resources/js/Customer/Index/UserBanner.jsx
rename to resources/js/Customer/Index/Partials/UserBanner.jsx
diff --git a/resources/js/Customer/Index/VoucherCard.jsx b/resources/js/Customer/Index/Partials/VoucherCard.jsx
similarity index 88%
rename from resources/js/Customer/Index/VoucherCard.jsx
rename to resources/js/Customer/Index/Partials/VoucherCard.jsx
index decaf0b..325cfb0 100644
--- a/resources/js/Customer/Index/VoucherCard.jsx
+++ b/resources/js/Customer/Index/Partials/VoucherCard.jsx
@@ -12,7 +12,7 @@ export default function VoucherCard({ voucher }) {
>
- {voucher.location.name}
+ {voucher.location_profile.location.name}
@@ -20,7 +20,7 @@ export default function VoucherCard({ voucher }) {
- {voucher.profile}
+ {voucher.location_profile.display_note}
IDR {formatIDR(voucher.validate_price)}
@@ -38,10 +38,10 @@ export default function VoucherCard({ voucher }) {
- {voucher.display_quota}
+ {voucher.location_profile.quota}
- {voucher.display_expired}
+ {voucher.location_profile.diplay_expired}
diff --git a/resources/js/Customer/Index/utils.js b/resources/js/Customer/Index/utils.js
new file mode 100644
index 0000000..a9eb50b
--- /dev/null
+++ b/resources/js/Customer/Index/utils.js
@@ -0,0 +1,19 @@
+import { router } from '@inertiajs/react'
+
+export const ALL = 0
+export const FAVORITE = 1
+
+export const handleBanner = (banner) => {
+ router.get(route('home.banner', banner))
+}
+
+export const isFavorite = (user, id) => {
+ if (user === null) {
+ return false
+ }
+ const isExists = user.location_favorites.findIndex((f) => f.id === id)
+ if (isExists !== -1) {
+ return true
+ }
+ return false
+}
diff --git a/resources/js/Customer/Profile/Index.jsx b/resources/js/Customer/Profile/Index.jsx
index 6bc174c..5bfffef 100644
--- a/resources/js/Customer/Profile/Index.jsx
+++ b/resources/js/Customer/Profile/Index.jsx
@@ -11,7 +11,7 @@ import {
import CustomerLayout from '@/Layouts/CustomerLayout'
import { useModalState } from '@/hooks'
import ModalConfirm from '@/Components/ModalConfirm'
-import BalanceBanner from '../Index/BalanceBanner'
+import BalanceBanner from '../Index/Partials/BalanceBanner'
export default function Index({ auth: { user }, notification_count }) {
const confirmModal = useModalState()
diff --git a/resources/js/Layouts/CustomerLayout.jsx b/resources/js/Layouts/CustomerLayout.jsx
index 8361348..8330dae 100644
--- a/resources/js/Layouts/CustomerLayout.jsx
+++ b/resources/js/Layouts/CustomerLayout.jsx
@@ -20,7 +20,7 @@ export default function CustomerLayout({ children }) {
} = usePage()
const handleOnClick = (r) => {
- router.get(route(r))
+ router.get(route(r, { direct: 1 }))
}
const isActive = (r) => {
diff --git a/resources/js/hooks.js b/resources/js/hooks.js
index 2f37452..1873146 100644
--- a/resources/js/hooks.js
+++ b/resources/js/hooks.js
@@ -1,25 +1,37 @@
-import { useState, useEffect } from "react";
+import { useState, useEffect, useRef } from 'react'
+
+export const useAutoFocus = () => {
+ const inputRef = useRef(null)
+
+ useEffect(() => {
+ if (inputRef.current) {
+ inputRef.current.focus()
+ }
+ }, [])
+
+ return inputRef
+}
export function useDebounce(value, delay) {
- const [debouncedValue, setDebouncedValue] = useState(value);
+ const [debouncedValue, setDebouncedValue] = useState(value)
useEffect(() => {
const handler = setTimeout(() => {
- setDebouncedValue(value);
- }, delay);
+ setDebouncedValue(value)
+ }, delay)
return () => {
- clearTimeout(handler);
- };
- }, [value, delay]);
- return debouncedValue;
+ clearTimeout(handler)
+ }
+ }, [value, delay])
+ return debouncedValue
}
export function useModalState(state = false) {
- const [isOpen, setIsOpen] = useState(state);
+ const [isOpen, setIsOpen] = useState(state)
const toggle = () => {
- setIsOpen(!isOpen);
- };
+ setIsOpen(!isOpen)
+ }
- const [data, setData] = useState(null);
+ const [data, setData] = useState(null)
return {
isOpen,
@@ -27,7 +39,7 @@ export function useModalState(state = false) {
setIsOpen,
data,
setData,
- };
+ }
}
export function usePagination(auth, r) {
@@ -37,18 +49,18 @@ export function usePagination(auth, r) {
links: [],
from: 0,
to: 0,
- total: 0
+ total: 0,
})
- const page = data.links.find(link => link.active === true)
+ const page = data.links.find((link) => link.active === true)
const fetch = (page = 1, params = {}) => {
setLoading(true)
axios
.get(route(r, { page: page, ...params }), {
headers: {
- "Content-Type": "application/json",
- Authorization: "Bearer " + auth.user.jwt_token,
+ 'Content-Type': 'application/json',
+ Authorization: 'Bearer ' + auth.user.jwt_token,
},
})
.then((res) => {
@@ -56,7 +68,7 @@ export function usePagination(auth, r) {
})
.catch((err) => console.log(err))
.finally(() => setLoading(false))
- };
+ }
return [data.data, data, page?.label, fetch, loading]
-}
\ No newline at end of file
+}
diff --git a/routes/web.php b/routes/web.php
index 8dd87a7..2813f22 100644
--- a/routes/web.php
+++ b/routes/web.php
@@ -29,6 +29,10 @@ Route::middleware(['http_secure_aware', 'guard_should_customer', 'inertia.custom
Route::get('/banner/{banner}', [HomeController::class, 'banner'])->name('home.banner');
Route::middleware('auth:customer')->group(function () {
+ // location to favorite
+ Route::post('/locations/{location}/add-favorite', [HomeController::class, 'addFavorite'])->name('customer.location.favorite');
+ Route::get('/favorites', [HomeController::class, 'favorite'])->name('customer.home.favorite');
+
// profile
Route::get('profile', [ProfileController::class, 'index'])->name('customer.profile.index');
Route::get('profile/update', [ProfileController::class, 'show'])->name('customer.profile.show');
diff --git a/rsync.sh b/rsync.sh
new file mode 100755
index 0000000..2ebc0b4
--- /dev/null
+++ b/rsync.sh
@@ -0,0 +1,4 @@
+#!/bin/bash
+rsync -arP -e 'ssh -p 225' --exclude=node_modules --exclude=database/database.sqlite --exclude=.git --exclude=.env --exclude=public/hot . arm@ajikamaludin.id:/home/arm/projects/www/voucher
+
+ssh -p 225 arm@ajikamaludin.id -C docker exec php82 php /var/www/voucher/artisan migrate:refresh --seed
\ No newline at end of file