diff --git a/app/Http/Controllers/ProfileController.php b/app/Http/Controllers/ProfileController.php index a48eb8d..e0f4c18 100644 --- a/app/Http/Controllers/ProfileController.php +++ b/app/Http/Controllers/ProfileController.php @@ -8,33 +8,36 @@ use Illuminate\Http\Request; use Illuminate\Support\Facades\Auth; use Illuminate\Support\Facades\Redirect; use Illuminate\View\View; +use App\Models\User; class ProfileController extends Controller { /** * Display the user's profile form. */ - public function edit(Request $request): View + public function edit(): View { return view('profile.edit', [ - 'user' => $request->user(), + 'user' => Auth::user() ]); } /** * Update the user's profile information. */ - public function update(ProfileUpdateRequest $request): RedirectResponse + public function update(Request $request): RedirectResponse { - $request->user()->fill($request->validated()); + $user = Auth::user(); - if ($request->user()->isDirty('email')) { - $request->user()->email_verified_at = null; - } + $validated = $request->validate([ + 'name' => ['required', 'string', 'max:255'], + 'email' => ['required', 'string', 'email', 'max:255', 'unique:users,email,' . $user->id], + ]); - $request->user()->save(); + $user->update($validated); - return Redirect::route('profile.edit')->with('status', 'profile-updated'); + return redirect()->route('profile.show') + ->with('success', 'Profile updated successfully.'); } /** @@ -55,6 +58,59 @@ class ProfileController extends Controller $request->session()->invalidate(); $request->session()->regenerateToken(); - return Redirect::to('/'); + return redirect()->to('/'); + } + + public function show(User $user = null): View + { + $user = $user ?? Auth::user(); + $recentActivity = collect(); + + // Get recent reviews + $recentReviews = $user->reviews() + ->with('game') + ->latest() + ->take(5) + ->get(); + + // Get recent ratings + $recentRatings = $user->ratings() + ->with('game') + ->latest() + ->take(5) + ->get(); + + // Get recent lists + $recentLists = $user->lists() + ->latest() + ->take(5) + ->get(); + + // Combine and sort all activity + $recentActivity = $recentActivity + ->concat($recentReviews) + ->concat($recentRatings) + ->concat($recentLists) + ->sortByDesc('created_at') + ->take(10); + + return view('profile.show', [ + 'user' => $user, + 'recentActivity' => $recentActivity + ]); + } + + public function reviews(): View + { + $user = Auth::user(); + $reviews = $user->reviews() + ->with('game') + ->latest() + ->paginate(10); + + return view('profile.reviews', [ + 'user' => $user, + 'reviews' => $reviews + ]); } } diff --git a/app/Models/User.php b/app/Models/User.php index 2e4e0b5..b9af0c9 100644 --- a/app/Models/User.php +++ b/app/Models/User.php @@ -7,6 +7,8 @@ use Illuminate\Database\Eloquent\Factories\HasFactory; use Illuminate\Foundation\Auth\User as Authenticatable; use Illuminate\Notifications\Notifiable; use Illuminate\Database\Eloquent\Relations\HasMany; +use Laravel\Sanctum\HasApiTokens; +use Illuminate\Support\Str; class User extends Authenticatable { @@ -20,6 +22,7 @@ class User extends Authenticatable */ protected $fillable = [ 'name', + 'slug', 'email', 'password', ]; @@ -44,6 +47,38 @@ class User extends Authenticatable 'password' => 'hashed', ]; + protected static function boot() + { + parent::boot(); + + static::creating(function ($user) { + $user->slug = Str::slug($user->name); + }); + + static::updating(function ($user) { + if ($user->isDirty('name')) { + $user->slug = Str::slug($user->name); + } + }); + + static::created(function ($user) { + // Create default lists + $defaultLists = [ + ['name' => 'Played Games', 'is_default' => true], + ['name' => 'Want to Play', 'is_default' => true] + ]; + + foreach ($defaultLists as $list) { + $user->lists()->create($list); + } + }); + } + + public function getRouteKeyName() + { + return 'slug'; + } + public function reviews(): HasMany { return $this->hasMany(Review::class); @@ -58,21 +93,4 @@ class User extends Authenticatable { return $this->hasMany(GameList::class); } - - protected static function boot() - { - parent::boot(); - - static::created(function ($user) { - // Create default lists - $defaultLists = [ - ['name' => 'Played Games', 'is_default' => true], - ['name' => 'Want to Play', 'is_default' => true] - ]; - - foreach ($defaultLists as $list) { - $user->lists()->create($list); - } - }); - } } diff --git a/database/migrations/2025_04_12_154639_add_slug_to_users_table.php b/database/migrations/2025_04_12_154639_add_slug_to_users_table.php new file mode 100644 index 0000000..96fd296 --- /dev/null +++ b/database/migrations/2025_04_12_154639_add_slug_to_users_table.php @@ -0,0 +1,28 @@ +string('slug')->unique()->after('name'); + }); + } + + /** + * Reverse the migrations. + */ + public function down(): void + { + Schema::table('users', function (Blueprint $table) { + $table->dropColumn('slug'); + }); + } +}; diff --git a/package-lock.json b/package-lock.json index dffac41..88f13b3 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,9 +1,12 @@ { - "name": "html", + "name": "cleargame", "lockfileVersion": 3, "requires": true, "packages": { "": { + "dependencies": { + "star-rating.js": "^4.3.1" + }, "devDependencies": { "@popperjs/core": "^2.11.8", "axios": "^1.6.4", @@ -777,6 +780,12 @@ "node": ">=0.4.0" } }, + "node_modules/detect-it": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/detect-it/-/detect-it-4.0.1.tgz", + "integrity": "sha512-dg5YBTJYvogK1+dA2mBUDKzOWfYZtHVba89SyZUhc4+e3i2tzgjANFg5lDRCd3UOtRcw00vUTMK8LELcMdicug==", + "license": "MIT" + }, "node_modules/dunder-proto": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz", @@ -1214,6 +1223,15 @@ "node": ">=0.10.0" } }, + "node_modules/star-rating.js": { + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/star-rating.js/-/star-rating.js-4.3.1.tgz", + "integrity": "sha512-ynf+5eyXIn1HKCoKy5gc5Cmu8l1mvw+bYhogzEMLNqZ2AAh7MLP51ZmJnt98/AzO3DZ5h2i/RgkbVPlBhDGebQ==", + "license": "MIT", + "dependencies": { + "detect-it": "^4.0.0" + } + }, "node_modules/vite": { "version": "5.4.18", "resolved": "https://registry.npmjs.org/vite/-/vite-5.4.18.tgz", diff --git a/package.json b/package.json index e371be6..cdcb1dd 100644 --- a/package.json +++ b/package.json @@ -11,5 +11,8 @@ "bootstrap": "^5.3.2", "laravel-vite-plugin": "^1.0.0", "vite": "^5.0.0" + }, + "dependencies": { + "star-rating.js": "^4.3.1" } } diff --git a/public/css/star-rating.css b/public/css/star-rating.css new file mode 100644 index 0000000..b2936fd --- /dev/null +++ b/public/css/star-rating.css @@ -0,0 +1,223 @@ +/** + * Star Rating + * @version: 4.3.0 + * @author: Paul Ryley (http://geminilabs.io) + * @url: https://github.com/pryley/star-rating.js + * @license: MIT + */ + +:root { + --gl-star-color: #fdd835; + --gl-star-color-inactive: #dcdce6; + --gl-star-empty: url('../img/star-empty.svg'); + --gl-star-full: url('../img/star-full.svg'); + --gl-star-size: 24px; + --gl-tooltip-background: rgba(17,17,17, .9); + --gl-tooltip-border-radius: 4px; + --gl-tooltip-color: #fff; + --gl-tooltip-font-size: 0.875rem; + --gl-tooltip-font-weight: 400; + --gl-tooltip-line-height: 1; + --gl-tooltip-margin: 12px; + --gl-tooltip-padding: .5em 1em; +} + +[data-star-rating] > select { + -webkit-clip-path: circle(1px at 0 0) !important; + clip-path: circle(1px at 0 0) !important; + clip: rect(1px, 1px, 1px, 1px) !important; + height: 1px !important; + margin: 0 !important; + overflow: hidden !important; + padding: 0 !important; + position: absolute !important; + top: 0 !important; + visibility: visible !important; + white-space: nowrap !important; + width: 1px !important; +} + +[data-star-rating] > select { + -webkit-appearance: none; + -moz-appearance: none; + appearance: none; + pointer-events: none; +} + +[data-star-rating] > select::before, +[data-star-rating] > select::after { + display: none !important; +} + +[data-star-rating].gl-star-rating--ltr > select { + left: 0 !important; +} + +[data-star-rating].gl-star-rating--rtl > select { + right: 0 !important; +} + +[data-star-rating] { + align-items: center; + display: flex; + position: relative; +} + +.gl-star-rating:not([data-star-rating]) .gl-star-rating--stars { + display: none; +} + +[data-star-rating] .gl-star-rating--stars { + align-items: center; + cursor: pointer; + display: flex; + position: relative; +} + +[data-star-rating] > select:focus + .gl-star-rating--stars span:first-child::before { + box-shadow: 0 0 0 3px -moz-mac-focusring; + box-shadow: 0 0 0 3px -webkit-focus-ring-color; + box-shadow: 0 0 0 3px Highlight; + content: ''; + display: block; + height: 100%; + outline: 1px solid transparent; + pointer-events: none; + position: absolute; + width: 100%; +} + +[data-star-rating] select[disabled] + .gl-star-rating--stars { + cursor: default; +} + +[data-star-rating] .gl-star-rating--stars > span { + display: flex; + height: 24px; + height: 24px; + height: var(--gl-star-size); + margin: 0; + width: 24px; + width: 24px; + width: var(--gl-star-size); +} + +[data-star-rating] .gl-star-rating--stars[aria-label]::before, +[data-star-rating] .gl-star-rating--stars[aria-label]::after { + -webkit-backface-visibility: hidden; + backface-visibility: hidden; + bottom: auto; + box-sizing: border-box; + left: 100%; + pointer-events: none; + position: absolute; + top: 50%; + transform-origin: top; + transform: translate3d(0,-50%,0); + white-space: nowrap; + z-index: 10; +} + +[data-star-rating] .gl-star-rating--stars[aria-label]::before { + background: rgba(17,17,17, .9); + background: rgba(17,17,17, .9); + background: var(--gl-tooltip-background); + -webkit-clip-path: path('M6 14.998c0-3-6-5.499-6-7.499S5.999 3 5.999 0L6 14.998z'); + clip-path: path('M6 14.998c0-3-6-5.499-6-7.499S5.999 3 5.999 0L6 14.998z'); + content: ''; + height: 15px; + margin: 0 0 0 6px; + width: 6px; +} + +[data-star-rating] .gl-star-rating--stars[aria-label]::after { + background: rgba(17,17,17, .9); + background: rgba(17,17,17, .9); + background: var(--gl-tooltip-background); + border-radius: 4px; + border-radius: 4px; + border-radius: var(--gl-tooltip-border-radius); + color: #fff; + color: #fff; + color: var(--gl-tooltip-color); + content: attr(aria-label); + font-size: 0.875rem; + font-size: 0.875rem; + font-size: var(--gl-tooltip-font-size); + font-weight: normal; + margin-left: 12px; + margin-left: 12px; + margin-left: var(--gl-tooltip-margin); + padding: .5em 1em; + padding: .5em 1em; + padding: var(--gl-tooltip-padding); + text-transform: none; +} + +[data-star-rating].gl-star-rating--rtl .gl-star-rating--stars[aria-label]::before, +[data-star-rating].gl-star-rating--rtl .gl-star-rating--stars[aria-label]::after { + left: auto; + right: 100%; +} + +[data-star-rating].gl-star-rating--rtl .gl-star-rating--stars[aria-label]::before { + transform: scaleX(-1) translate3d(0,-50%,0); + margin: 0 6px 0 0; +} + +[data-star-rating].gl-star-rating--rtl .gl-star-rating--stars[aria-label]::after { + margin-left: 0; + margin-right: 12px; + margin-right: 12px; + margin-right: var(--gl-tooltip-margin); +} + +[data-star-rating] svg { + height: 100%; + width: 100%; +} + +[data-star-rating] .gl-star-half { + fill: none; + stroke: none; +} + +[data-star-rating] .gl-star-full { + fill: #dcdce6; + fill: #dcdce6; + fill: var(--gl-star-color-inactive); + stroke: #dcdce6; + stroke: #dcdce6; + stroke: var(--gl-star-color-inactive); + transition: fill 0.15s ease-in-out, stroke 0.15s ease-in-out; +} + +[data-star-rating] .gl-active .gl-star-full { + fill: #fdd835; + fill: #fdd835; + fill: var(--gl-star-color); + stroke: #fdd835; + stroke: #fdd835; + stroke: var(--gl-star-color); +} + +/* Compatibilty with v3 */ + +.gl-star-rating--stars[class*=" s"] > span { + background-image: url('../img/star-empty.svg') !important; + background-image: url('../img/star-empty.svg') !important; + background-image: var(--gl-star-empty) !important; +} + +.gl-star-rating--stars[class*=" s"] > span { + background-position: center; + background-repeat: no-repeat; + background-size: 90%; +} + +.gl-star-rating--stars[class*=" s"] > span.gl-active, +.gl-star-rating--stars[class*=" s"] > span.gl-active.gl-selected { + background-image: url('../img/star-full.svg') !important; + background-image: url('../img/star-full.svg') !important; + background-image: var(--gl-star-full) !important; +} diff --git a/public/js/star-rating.js b/public/js/star-rating.js new file mode 100644 index 0000000..b3b7c42 --- /dev/null +++ b/public/js/star-rating.js @@ -0,0 +1,487 @@ +var StarRating = (function () { + 'use strict'; + + function _classCallCheck(instance, Constructor) { + if (!(instance instanceof Constructor)) { + throw new TypeError("Cannot call a class as a function"); + } + } + + function _defineProperties(target, props) { + for (var i = 0; i < props.length; i++) { + var descriptor = props[i]; + descriptor.enumerable = descriptor.enumerable || false; + descriptor.configurable = true; + if ("value" in descriptor) descriptor.writable = true; + Object.defineProperty(target, descriptor.key, descriptor); + } + } + + function _createClass(Constructor, protoProps, staticProps) { + if (protoProps) _defineProperties(Constructor.prototype, protoProps); + if (staticProps) _defineProperties(Constructor, staticProps); + return Constructor; + } + + var defaults = { + classNames: { + active: 'gl-active', + base: 'gl-star-rating', + selected: 'gl-selected' + }, + clearable: true, + maxStars: 10, + prebuilt: false, + stars: null, + tooltip: 'Select a Rating' + }; + + var addRemoveClass = function addRemoveClass(el, bool, className) { + el.classList[bool ? 'add' : 'remove'](className); + }; + var createSpanEl = function createSpanEl(attributes) { + var el = document.createElement('span'); + attributes = attributes || {}; + + for (var key in attributes) { + el.setAttribute(key, attributes[key]); + } + + return el; + }; + var inRange = function inRange(value, min, max) { + return /^\d+$/.test(value) && min <= value && value <= max; + }; + var insertSpanEl = function insertSpanEl(el, after, attributes) { + var newEl = createSpanEl(attributes); + el.parentNode.insertBefore(newEl, after ? el.nextSibling : el); + return newEl; + }; + var merge = function merge() { + for (var _len = arguments.length, args = new Array(_len), _key = 0; _key < _len; _key++) { + args[_key] = arguments[_key]; + } + + // adapted from https://github.com/firstandthird/aug + var results = {}; + args.forEach(function (prop) { + Object.keys(prop || {}).forEach(function (propName) { + if (args[0][propName] === undefined) return; // restrict keys to the defaults + + var propValue = prop[propName]; + + if (type(propValue) === 'Object' && type(results[propName]) === 'Object') { + results[propName] = merge(results[propName], propValue); + return; + } + + results[propName] = propValue; + }); + }); + return results; + }; + var type = function type(value) { + return {}.toString.call(value).slice(8, -1); + }; + var values = function values(selectEl) { + var values = []; + [].forEach.call(selectEl.options, function (el) { + var value = parseInt(el.value, 10) || 0; + + if (value > 0) { + values.push({ + index: el.index, + text: el.text, + value: value + }); + } + }); + return values.sort(function (a, b) { + return a.value - b.value; + }); + }; + + var Widget = /*#__PURE__*/function () { + function Widget(el, props) { + _classCallCheck(this, Widget); + + // (HTMLElement, object):void + this.direction = window.getComputedStyle(el, null).getPropertyValue('direction'); + this.el = el; + this.events = { + change: this.onChange.bind(this), + keydown: this.onKeyDown.bind(this), + mousedown: this.onPointerDown.bind(this), + mouseleave: this.onPointerLeave.bind(this), + mousemove: this.onPointerMove.bind(this), + reset: this.onReset.bind(this), + touchend: this.onPointerDown.bind(this), + touchmove: this.onPointerMove.bind(this) + }; + this.indexActive = null; // the active span index + + this.indexSelected = null; // the selected span index + + this.props = props; + this.tick = null; + this.ticking = false; + this.values = values(el); + this.widgetEl = null; + + if (this.el.widget) { + this.el.widget.destroy(); // remove any stale event listeners + } + + if (inRange(this.values.length, 1, this.props.maxStars)) { + this.build(); + } else { + this.destroy(); + } + } + + _createClass(Widget, [{ + key: "build", + value: function build() { + // ():void + this.destroy(); + this.buildWidget(); + this.selectValue(this.indexSelected = this.selected(), false); // set the initial value but do not trigger change event + + this.handleEvents('add'); + this.el.widget = this; // store a reference to this widget on the SELECT so that we can remove stale event listeners + } + }, { + key: "buildWidget", + value: function buildWidget() { + var _this = this; + + // ():void + var parentEl = null; + var widgetEl = null; + + if (this.props.prebuilt) { + parentEl = this.el.parentNode; + widgetEl = parentEl.querySelector('.' + this.props.classNames.base + '--stars'); + } + + if (null === widgetEl) { + parentEl = insertSpanEl(this.el, false, { + "class": this.props.classNames.base + }); + parentEl.appendChild(this.el); + widgetEl = insertSpanEl(this.el, true, { + "class": this.props.classNames.base + '--stars' + }); + this.values.forEach(function (item, index) { + var el = createSpanEl({ + 'data-index': index, + 'data-value': item.value + }); + + if ('function' === typeof _this.props.stars) { + _this.props.stars.call(_this, el, item, index); + } + + [].forEach.call(el.children, function (el) { + return el.style.pointerEvents = 'none'; + }); + widgetEl.innerHTML += el.outerHTML; + }); + } + + parentEl.dataset.starRating = ''; + parentEl.classList.add(this.props.classNames.base + '--' + this.direction); + + if (this.props.tooltip) { + widgetEl.setAttribute('role', 'tooltip'); + } + + this.widgetEl = widgetEl; + } + }, { + key: "changeIndexTo", + value: function changeIndexTo(index, force) { + var _this2 = this; + + // (int):void + if (this.indexActive !== index || force) { + [].forEach.call(this.widgetEl.children, function (el, i) { + // i starts at zero + addRemoveClass(el, i <= index, _this2.props.classNames.active); + addRemoveClass(el, i === _this2.indexSelected, _this2.props.classNames.selected); + }); + this.widgetEl.setAttribute('data-rating', index + 1); + + if ('function' !== typeof this.props.stars && !this.props.prebuilt) { + // @v3 compat + this.widgetEl.classList.remove('s' + 10 * (this.indexActive + 1)); + this.widgetEl.classList.add('s' + 10 * (index + 1)); + } + + if (this.props.tooltip) { + var _this$values$index; + + var label = index < 0 ? this.props.tooltip : (_this$values$index = this.values[index]) === null || _this$values$index === void 0 ? void 0 : _this$values$index.text; + this.widgetEl.setAttribute('aria-label', label); + } + + this.indexActive = index; + } + + this.ticking = false; + } + }, { + key: "destroy", + value: function destroy() { + // ():void + this.indexActive = null; // the active span index + + this.indexSelected = this.selected(); // the selected span index + + var parentEl = this.el.parentNode; + + if (parentEl.classList.contains(this.props.classNames.base)) { + if (this.props.prebuilt) { + this.widgetEl = parentEl.querySelector('.' + this.props.classNames.base + '--stars'); + parentEl.classList.remove(this.props.classNames.base + '--' + this.direction); + delete parentEl.dataset.starRating; + } else { + parentEl.parentNode.replaceChild(this.el, parentEl); + } + + this.handleEvents('remove'); + } + + delete this.el.widget; // remove the widget reference + } + }, { + key: "eventListener", + value: function eventListener(el, action, events, items) { + var _this3 = this; + + // (HTMLElement, string, array, object):void + events.forEach(function (ev) { + return el[action + 'EventListener'](ev, _this3.events[ev], items || false); + }); + } + }, { + key: "handleEvents", + value: function handleEvents(action) { + // (string):void + var formEl = this.el.closest('form'); + + if (formEl && formEl.tagName === 'FORM') { + this.eventListener(formEl, action, ['reset']); + } + + this.eventListener(this.el, action, ['change']); // always trigger the change event, even when SELECT is disabled + + if ('add' === action && this.el.disabled) return; + this.eventListener(this.el, action, ['keydown']); + this.eventListener(this.widgetEl, action, ['mousedown', 'mouseleave', 'mousemove', 'touchend', 'touchmove'], false); + } + }, { + key: "indexFromEvent", + value: function indexFromEvent(ev) { + var _ev$touches, _ev$changedTouches; + + // (MouseEvent|TouchEvent):void + var origin = ((_ev$touches = ev.touches) === null || _ev$touches === void 0 ? void 0 : _ev$touches[0]) || ((_ev$changedTouches = ev.changedTouches) === null || _ev$changedTouches === void 0 ? void 0 : _ev$changedTouches[0]) || ev; + var el = document.elementFromPoint(origin.clientX, origin.clientY); + + if (el.parentNode === this.widgetEl) { + return [].slice.call(el.parentNode.children).indexOf(el); + } + + return this.indexActive; + } + }, { + key: "onChange", + value: function onChange() { + // ():void + this.changeIndexTo(this.selected(), true); + } + }, { + key: "onKeyDown", + value: function onKeyDown(ev) { + // (KeyboardEvent):void + var key = ev.key.slice(5); + if (!~['Left', 'Right'].indexOf(key)) return; + ev.preventDefault(); + var increment = key === 'Left' ? -1 : 1; + + if (this.direction === 'rtl') { + increment *= -1; + } + + var maxIndex = this.values.length - 1; + var minIndex = -1; + var index = Math.min(Math.max(this.selected() + increment, minIndex), maxIndex); + this.selectValue(index, true); // trigger change event + } + }, { + key: "onPointerDown", + value: function onPointerDown(ev) { + // (MouseEvent|TouchEvent):void + ev.preventDefault(); // this.el.focus(); // highlight the rating field + + var index = this.indexFromEvent(ev); + + if (this.props.clearable && index === this.indexSelected) { + index = -1; // remove the value + } + + this.selectValue(index, true); // trigger change event + } + }, { + key: "onPointerLeave", + value: function onPointerLeave(ev) { + var _this4 = this; + + // (MouseEvent):void + ev.preventDefault(); + cancelAnimationFrame(this.tick); + requestAnimationFrame(function () { + return _this4.changeIndexTo(_this4.indexSelected); + }); + } + }, { + key: "onPointerMove", + value: function onPointerMove(ev) { + var _this5 = this; + + // (MouseEvent|TouchEvent):void + ev.preventDefault(); + + if (!this.ticking) { + this.tick = requestAnimationFrame(function () { + return _this5.changeIndexTo(_this5.indexFromEvent(ev)); + }); + this.ticking = true; + } + } + }, { + key: "onReset", + value: function onReset() { + var _this$el$querySelecto; + + // ():void + var index = this.valueIndex((_this$el$querySelecto = this.el.querySelector('[selected]')) === null || _this$el$querySelecto === void 0 ? void 0 : _this$el$querySelecto.value); + this.selectValue(index || -1, false); // do not trigger change event + } + }, { + key: "selected", + value: function selected() { + // ():int + return this.valueIndex(this.el.value); // get the selected span index + } + }, { + key: "selectValue", + value: function selectValue(index, triggerChangeEvent) { + var _this$values$index2; + + // (int, bool):void + this.el.value = ((_this$values$index2 = this.values[index]) === null || _this$values$index2 === void 0 ? void 0 : _this$values$index2.value) || ''; // first set the new value + + this.indexSelected = this.selected(); // get the actual index from the selected value + + if (false === triggerChangeEvent) { + this.changeIndexTo(this.selected(), true); + } else { + this.el.dispatchEvent(new Event('change')); + } + } + }, { + key: "valueIndex", + value: function valueIndex(value) { + return this.values.findIndex(function (val) { + return val.value === +value; + }); + } + }]); + + return Widget; + }(); + + var StarRating = /*#__PURE__*/function () { + function StarRating(selector, props) { + _classCallCheck(this, StarRating); + + // (HTMLSelectElement|NodeList|string, object):void + this.destroy = this.destroy.bind(this); + this.props = props; + this.rebuild = this.rebuild.bind(this); + this.selector = selector; + this.widgets = []; + this.build(); + } + + _createClass(StarRating, [{ + key: "build", + value: function build() { + var _this = this; + + // (HTMLSelectElement|NodeList|string, object):void + this.queryElements(this.selector).forEach(function (el) { + var options = merge(defaults, _this.props, JSON.parse(el.getAttribute('data-options'))); + + if ('SELECT' === el.tagName && !el.widget) { + // check for an existing Widget reference + if (!options.prebuilt && el.parentNode.classList.contains(options.classNames.base)) { + _this.unwrap(el); + } + + _this.widgets.push(new Widget(el, options)); + } + }); + } + }, { + key: "destroy", + value: function destroy() { + // ():void + this.widgets.forEach(function (widget) { + return widget.destroy(); + }); + this.widgets = []; + } + }, { + key: "queryElements", + value: function queryElements(selector) { + // (HTMLSelectElement|NodeList|string):array + if ('HTMLSelectElement' === type(selector)) { + return [selector]; + } + + if ('NodeList' === type(selector)) { + return [].slice.call(selector); + } + + if ('String' === type(selector)) { + return [].slice.call(document.querySelectorAll(selector)); + } + + return []; + } + }, { + key: "rebuild", + value: function rebuild() { + // ():void + this.destroy(); + this.build(); + } + }, { + key: "unwrap", + value: function unwrap(el) { + var removeEl = el.parentNode; + var parentEl = removeEl.parentNode; + parentEl.insertBefore(el, removeEl); + parentEl.removeChild(removeEl); + } + }]); + + return StarRating; + }(); + + return StarRating; + +}()); diff --git a/resources/views/games/show.blade.php b/resources/views/games/show.blade.php index 23ee196..058a600 100644 --- a/resources/views/games/show.blade.php +++ b/resources/views/games/show.blade.php @@ -146,26 +146,64 @@
No reviews yet. Be the first to review this game!
@else @foreach($game->reviews as $review) -