From: Daniel Karbach Date: Mon, 23 Jun 2025 19:34:45 +0000 (+0200) Subject: show episodes on user profile X-Git-Url: https://git.localhorst.tv/?a=commitdiff_plain;h=4fe8a79ace88acd6cbd1261b701a98d8f368388c;p=alttp.git show episodes on user profile --- diff --git a/app/Http/Controllers/UserController.php b/app/Http/Controllers/UserController.php index 01b8f06..3fe68a5 100644 --- a/app/Http/Controllers/UserController.php +++ b/app/Http/Controllers/UserController.php @@ -68,18 +68,104 @@ class UserController extends Controller } public function single(Request $request, $id) { - $user = User::findOrFail($id); + $user = is_numeric($id) ? User::findOrFail($id) : User::where('username', '=', $id)->firstOrFail(); $this->authorize('view', $user); + + $validatedData = $request->validate([ + 'with' => 'array', + 'with.*' => 'string|in:episodes_as_comms,episodes_as_crew,episodes_as_setup,episodes_as_tracker,episodes_as_runner', + ]); + $user->append('random_quote'); - $user->load('participation'); - $user->load('participation.tournament'); - $user->loadCount('round_first'); - $user->loadCount('round_second'); - $user->loadCount('round_third'); - $user->loadCount('tournament_first'); - $user->loadCount('tournament_second'); - $user->loadCount('tournament_third'); - return $user->toJson(); + $user->load([ + 'participation', + 'participation.tournament', + ]); + $user->loadCount([ + 'round_first', + 'round_second', + 'round_third', + 'tournament_first', + 'tournament_second', + 'tournament_third', + ]); + + $json = $user->toArray(); + + if (!empty($validatedData['with'])) { + if (in_array('episodes_as_crew', $validatedData['with'])) { + $json['episodes_as_crew'] = $user->episodes_as_crew()->with([ + 'channels', + 'crew' => function ($query) use ($request) { + $query->where('confirmed', true); + $query->orWhere('user_id', '=', $request->user()->id); + $query->orWhereIn('channel_id', $request->user()->channel_crews->pluck('channel_id')); + }, + 'crew.user', + 'event', + 'players', + 'players.user', + ])->orderBy('start', 'DESC')->limit(50)->get(); + } + if (in_array('episodes_as_comms', $validatedData['with'])) { + $json['episodes_as_comms'] = $user->episodes_as_comms()->with([ + 'channels', + 'crew' => function ($query) use ($request) { + $query->where('confirmed', true); + $query->orWhere('user_id', '=', $request->user()->id); + $query->orWhereIn('channel_id', $request->user()->channel_crews->pluck('channel_id')); + }, + 'crew.user', + 'event', + 'players', + 'players.user', + ])->orderBy('start', 'DESC')->limit(50)->get(); + } + if (in_array('episodes_as_setup', $validatedData['with'])) { + $json['episodes_as_setup'] = $user->episodes_as_setup()->with([ + 'channels', + 'crew' => function ($query) use ($request) { + $query->where('confirmed', true); + $query->orWhere('user_id', '=', $request->user()->id); + $query->orWhereIn('channel_id', $request->user()->channel_crews->pluck('channel_id')); + }, + 'crew.user', + 'event', + 'players', + 'players.user', + ])->orderBy('start', 'DESC')->limit(50)->get(); + } + if (in_array('episodes_as_tracker', $validatedData['with'])) { + $json['episodes_as_tracker'] = $user->episodes_as_tracker()->with([ + 'channels', + 'crew' => function ($query) use ($request) { + $query->where('confirmed', true); + $query->orWhere('user_id', '=', $request->user()->id); + $query->orWhereIn('channel_id', $request->user()->channel_crews->pluck('channel_id')); + }, + 'crew.user', + 'event', + 'players', + 'players.user', + ])->orderBy('start', 'DESC')->limit(50)->get(); + } + if (in_array('episodes_as_runner', $validatedData['with'])) { + $json['episodes_as_runner'] = $user->episodes_as_runner()->with([ + 'channels', + 'crew' => function ($query) use ($request) { + $query->where('confirmed', true); + $query->orWhere('user_id', '=', $request->user()->id); + $query->orWhereIn('channel_id', $request->user()->channel_crews->pluck('channel_id')); + }, + 'crew.user', + 'event', + 'players', + 'players.user', + ])->orderBy('start', 'DESC')->limit(50)->get(); + } + } + + return $json; } } diff --git a/app/Models/User.php b/app/Models/User.php index 9360255..6ed6a81 100644 --- a/app/Models/User.php +++ b/app/Models/User.php @@ -124,6 +124,31 @@ class User extends Authenticatable return $this->hasMany(ChannelCrew::class); } + public function episodes_as_crew() { + return $this + ->belongsToMany(Episode::class, 'episode_crews') + ->wherePivot('episode_crews.confirmed', '=', 1) + ->where('episodes.confirmed', '=', 1); + } + + public function episodes_as_comms() { + return $this->episodes_as_crew()->wherePivot('episode_crews.role', '=', 'commentary'); + } + + public function episodes_as_setup() { + return $this->episodes_as_crew()->wherePivot('episode_crews.role', '=', 'setup'); + } + + public function episodes_as_tracker() { + return $this->episodes_as_crew()->wherePivot('episode_crews.role', '=', 'tracking'); + } + + public function episodes_as_runner() { + return $this + ->belongsToMany(Episode::class, 'episode_players') + ->where('episodes.confirmed', '=', 1); + } + public function participation() { return $this->hasMany(Participant::class); } diff --git a/eslint.config.mjs b/eslint.config.mjs index cd3f9a1..7214e21 100644 --- a/eslint.config.mjs +++ b/eslint.config.mjs @@ -1,11 +1,17 @@ +import { defineConfig } from 'eslint/config'; import js from '@eslint/js'; import react from 'eslint-plugin-react'; import globals from 'globals'; -export default [ - js.configs.recommended, - react.configs.flat.recommended, +export default defineConfig([ { + extends: [ + js.configs.recommended, + react.configs.flat.recommended, + ], + + files: ['resources/js/**/*.{js,jsx}'], + plugins: { react, }, @@ -31,4 +37,4 @@ export default [ }, }, }, -]; +]); diff --git a/resources/js/app/User.jsx b/resources/js/app/User.jsx index 402178a..0b6f098 100644 --- a/resources/js/app/User.jsx +++ b/resources/js/app/User.jsx @@ -14,7 +14,7 @@ const User = () => { return user ? <> - : - i18n.t('users.noStream') - } - {' '} - -

- - -

{i18n.t('users.tournamentRecords')}

- - - -

{i18n.t('users.roundRecords')}

- - - -

{i18n.t('users.tournaments')}

- - - -; +const Profile = ({ user }) => { + const [activeEpisodes, setActiveEpisodes] = React.useState('runner'); + + const { t } = useTranslation(); + + React.useEffect(() => { + if (!user.episodes_as_runner.length) { + if (user.episodes_as_comms.length) { + setActiveEpisodes('comms'); + } else if (user.episodes_as_tracker.length) { + setActiveEpisodes('tracker'); + } else if (user.episodes_as_setup.length) { + setActiveEpisodes('setup'); + } + } else { + setActiveEpisodes('runner'); + } + }, [user]) + + const participation = React.useMemo(() => { + let p = 0; + if (user.episodes_as_runner.length) ++p; + if (user.episodes_as_comms.length) ++p; + if (user.episodes_as_tracker.length) ++p; + if (user.episodes_as_setup.length) ++p; + return p; + }, [user]); + + + return +

+ {user.nickname || user.username} + {' '} + +

+ {user.random_quote && user.random_quote.comment ? + +
+ {user.random_quote.comment} +
+
+ : null} + + +

{t('users.discordTag')}

+ + + +

{t('users.streamLink')}

+

+ {user.stream_link ? + + : + t('users.noStream') + } + {' '} + +

+ + +

{t('users.tournamentRecords')}

+ + + +

{t('users.roundRecords')}

+ + + +

{t('users.tournaments')}

+ + + {participation > 0 ? + +

{t('users.episodes')}

+ {participation > 1 ? + + {user.episodes_as_runner.length ? + + : null} + {user.episodes_as_comms.length ? + + : null} + {user.episodes_as_tracker.length ? + + : null} + {user.episodes_as_setup.length ? + + : null} + + : null} + {activeEpisodes === 'runner' ? + + : null} + {activeEpisodes === 'comms' ? + + : null} + {activeEpisodes === 'tracker' ? + + : null} + {activeEpisodes === 'setup' ? + + : null} + + : null} +
+
; +}; Profile.propTypes = { user: PropTypes.shape({ + episodes_as_comms: PropTypes.arrayOf(PropTypes.shape({ + })), + episodes_as_runner: PropTypes.arrayOf(PropTypes.shape({ + })), + episodes_as_setup: PropTypes.arrayOf(PropTypes.shape({ + })), + episodes_as_tracker: PropTypes.arrayOf(PropTypes.shape({ + })), nickname: PropTypes.string, participation: PropTypes.arrayOf(PropTypes.shape({ })), @@ -91,4 +182,4 @@ Profile.propTypes = { }), }; -export default withTranslation()(Profile); +export default Profile; diff --git a/resources/js/i18n/de.js b/resources/js/i18n/de.js index c05f87e..6c17132 100644 --- a/resources/js/i18n/de.js +++ b/resources/js/i18n/de.js @@ -66,6 +66,11 @@ export default { }, button: { add: 'Hinzufügen', + asComms: 'Als Kommentator', + asCrew: 'Als Crew', + asRunner: 'Als Runner', + asSetup: 'Als Setup-Helper', + asTracker: 'Als Tracker', back: 'Zurück', browserSource: 'Browser Source', cancel: 'Abbrechen', @@ -891,6 +896,7 @@ export default { discordTag: 'Discord Tag', editNickname: 'Name bearbeiten', editStreamLink: 'Stream Link bearbeiten', + episodes: 'Restreams', nickname: 'Name', noStream: 'Kein Stream gesetzt', participationEmpty: 'Hat noch an keinen Turnieren teilgenommen.', diff --git a/resources/js/i18n/en.js b/resources/js/i18n/en.js index 08783ce..1341118 100644 --- a/resources/js/i18n/en.js +++ b/resources/js/i18n/en.js @@ -66,6 +66,11 @@ export default { }, button: { add: 'Add', + asComms: 'As Commentary', + asCrew: 'As Crew', + asRunner: 'As Runner', + asSetup: 'As Setup Helper', + asTracker: 'As Tracker', back: 'Back', browserSource: 'Browser source', cancel: 'Cancel', @@ -891,6 +896,7 @@ export default { discordTag: 'Discord tag', editNickname: 'Edit name', editStreamLink: 'Edit stream link', + episodes: 'Restreams', nickname: 'Name', noStream: 'No stream set', participationEmpty: 'Has not participated in any tourneys yet.', diff --git a/resources/js/pages/User.jsx b/resources/js/pages/User.jsx index 9e8d22a..58796c8 100644 --- a/resources/js/pages/User.jsx +++ b/resources/js/pages/User.jsx @@ -22,7 +22,17 @@ const User = () => { setLoading(true); const ctrl = new AbortController(); axios - .get(`/api/users/${id}`, { signal: ctrl.signal }) + .get(`/api/users/${id}`, { + params: { + with: [ + 'episodes_as_comms', + 'episodes_as_runner', + 'episodes_as_setup', + 'episodes_as_tracker', + ], + }, + signal: ctrl.signal, + }) .then(response => { setError(null); setLoading(false); diff --git a/resources/sass/episodes.scss b/resources/sass/episodes.scss index 6caf0dd..02d2a70 100644 --- a/resources/sass/episodes.scss +++ b/resources/sass/episodes.scss @@ -8,6 +8,11 @@ z-index: 1; } +.episodes-list.compact .episodes-group-heading { + padding-bottom: 1rem; + background: linear-gradient(180deg, $body-bg, $body-bg 75%, transparent); +} + .episodes-item { background-size: 6rem auto; background-repeat: no-repeat;