Skip to content

Commit

Permalink
chore(dashboard): add twitch user select shadcn component
Browse files Browse the repository at this point in the history
[skip ci]
  • Loading branch information
Satont committed May 10, 2024
1 parent 4ca26b1 commit 6733a4e
Show file tree
Hide file tree
Showing 6 changed files with 1,147 additions and 365 deletions.
2 changes: 1 addition & 1 deletion frontend/dashboard/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@
"naive-ui": "2.38.1",
"nested-css-to-flat": "1.0.5",
"plyr": "3.7.8",
"radix-vue": "1.7.0",
"radix-vue": "1.7.4",
"tailwind-merge": "2.2.2",
"tailwindcss-animate": "1.0.7",
"vee-validate": "4.12.6",
Expand Down
187 changes: 98 additions & 89 deletions frontend/dashboard/src/api/twitch.ts
Original file line number Diff line number Diff line change
@@ -1,94 +1,103 @@
import { useMutation, useQuery } from '@tanstack/vue-query';
import type { GetResponse as RewardsResponse } from '@twir/api/messages/rewards/rewards';
import type {
TwitchGetUsersResponse,
TwitchSearchChannelsRequest,
TwitchSearchChannelsResponse,
} from '@twir/api/messages/twitch/twitch';
import { ComputedRef, isRef, MaybeRef, Ref } from 'vue';
import { useMutation, useQuery } from '@tanstack/vue-query'
import { isRef } from 'vue'

import { protectedApiClient, unprotectedApiClient } from '@/api/twirp.js';
import type { GetResponse as RewardsResponse } from '@twir/api/messages/rewards/rewards'
import type { TwitchGetUsersResponse, TwitchSearchChannelsRequest, TwitchSearchChannelsResponse } from '@twir/api/messages/twitch/twitch'
import type { ComputedRef, MaybeRef, Ref } from 'vue'

import { protectedApiClient, unprotectedApiClient } from '@/api/twirp.js'

type TwitchIn = Ref<string[]> | Ref<string> | ComputedRef<string> | ComputedRef<string[]> | string[]
export const useTwitchGetUsers = (opts: {
ids?: TwitchIn,
export function useTwitchGetUsers(opts: {
ids?: TwitchIn
names?: TwitchIn
}) => useQuery({
queryKey: ['twitch', 'search', 'users', opts.ids, opts.names],
queryFn: async (): Promise<TwitchGetUsersResponse> => {
let ids = isRef(opts?.ids)
? Array.isArray(opts.ids.value) ? opts.ids.value : [opts.ids.value]
: opts?.ids ?? [''];
let names = isRef(opts?.names)
? Array.isArray(opts.names.value) ? opts.names.value : [opts.names.value]
: opts?.names ?? [''];

names = names.filter(n => n !== '');
ids = ids.filter(n => n !== '');

if (ids.length === 0 && names.length === 0) {
return {
users: [],
};
}

const call = await unprotectedApiClient.twitchGetUsers({
ids,
names,
});

return call.response;
},
});

export const useTwitchSearchChannels = (params: Ref<TwitchSearchChannelsRequest>) => useQuery({
queryKey: ['twitch', 'search', 'channels', params],
queryFn: async (): Promise<TwitchSearchChannelsResponse> => {
const rawParams = isRef(params) ? params.value : params;

if (!rawParams.query) {
return { channels: [] };
}

const call = await unprotectedApiClient.twitchSearchChannels(rawParams);
return call.response;
},
});

export const useTwitchRewards = () => useQuery({
queryKey: ['twitchRewards'],
queryFn: async (): Promise<RewardsResponse> => {
const call = await protectedApiClient.rewardsGet({});
return call.response;
},
});

export const useTwitchSearchCategories = (query: string | Ref<string>) => useQuery({
queryKey: ['twitchSearchCategories', query || ''],
queryFn: async () => {
const input = isRef(query) ? query.value : query;
if (!input) return { categories: [] };

const call = await protectedApiClient.twitchSearchCategories({ query: input });
return call.response;
},
});

export const useTwitchGetCategories = (ids: MaybeRef<string[]> | ComputedRef<string[]>) => useQuery({
queryKey: ['twitchGetCategories', ids || ''],
queryFn: async () => {
const input = isRef(ids) ? ids.value : ids;
if (!input) return { categories: [] };

const call = await protectedApiClient.twitchGetCategories({ ids: input });
return call.response;
},
});

export const twitchSetChannelInformationMutation = () => useMutation({
mutationKey: ['twitchSetChannelInformation'],
mutationFn: async (req: { categoryId: string, title: string }) => {
await protectedApiClient.twitchSetChannelInformation(req);
},
});
}) {
return useQuery({
queryKey: ['twitch', 'search', 'users', opts.ids, opts.names],
queryFn: async (): Promise<TwitchGetUsersResponse> => {
let ids = isRef(opts?.ids)
? Array.isArray(opts.ids.value) ? opts.ids.value : [opts.ids.value]
: opts?.ids ?? ['']
let names = isRef(opts?.names)
? Array.isArray(opts.names.value) ? opts.names.value : [opts.names.value]
: opts?.names ?? ['']

names = names.filter(n => n !== '')
ids = ids.filter(n => n !== '')

if (ids.length === 0 && names.length === 0) {
return {
users: [],
}
}

const call = await unprotectedApiClient.twitchGetUsers({
ids,
names,
})

return call.response
},
})
}

export function useTwitchSearchChannels(params: Ref<TwitchSearchChannelsRequest>) {
return useQuery({
queryKey: ['twitch', 'search', 'channels', params],
queryFn: async (): Promise<TwitchSearchChannelsResponse> => {
const rawParams = isRef(params) ? params.value : params

if (!rawParams.query) {
return { channels: [] }
}

const call = await unprotectedApiClient.twitchSearchChannels(rawParams)
return call.response
},
})
}

export function useTwitchRewards() {
return useQuery({
queryKey: ['twitchRewards'],
queryFn: async (): Promise<RewardsResponse> => {
const call = await protectedApiClient.rewardsGet({})
return call.response
},
})
}

export function useTwitchSearchCategories(query: string | Ref<string>) {
return useQuery({
queryKey: ['twitchSearchCategories', query || ''],
queryFn: async () => {
const input = isRef(query) ? query.value : query
if (!input) return { categories: [] }

const call = await protectedApiClient.twitchSearchCategories({ query: input })
return call.response
},
})
}

export function useTwitchGetCategories(ids: MaybeRef<string[]> | ComputedRef<string[]>) {
return useQuery({
queryKey: ['twitchGetCategories', ids || ''],
queryFn: async () => {
const input = isRef(ids) ? ids.value : ids
if (!input) return { categories: [] }

const call = await protectedApiClient.twitchGetCategories({ ids: input })
return call.response
},
})
}

export function twitchSetChannelInformationMutation() {
return useMutation({
mutationKey: ['twitchSetChannelInformation'],
mutationFn: async (req: { categoryId: string, title: string }) => {
await protectedApiClient.twitchSetChannelInformation(req)
},
})
}
78 changes: 38 additions & 40 deletions frontend/dashboard/src/components/twitchUsers/multiple.vue
Original file line number Diff line number Diff line change
@@ -1,74 +1,72 @@
<script setup lang="ts">
import type { Channel, TwitchSearchChannelsRequest, TwitchUser } from '@twir/api/messages/twitch/twitch';
import { refDebounced } from '@vueuse/core';
import { NSelect, NTag, NAvatar } from 'naive-ui';
import { computed, ref, h } from 'vue';
import { refDebounced } from '@vueuse/core'
import { NAvatar, NSelect, NTag } from 'naive-ui'
import { computed, h, ref } from 'vue'
import { useTwitchSearchChannels, useTwitchGetUsers } from '@/api/index.js';
import { resolveUserName } from '@/helpers';
import type { Channel, TwitchSearchChannelsRequest, TwitchUser } from '@twir/api/messages/twitch/twitch'
import { useTwitchGetUsers, useTwitchSearchChannels } from '@/api/index.js'
import { resolveUserName } from '@/helpers'
// eslint-disable-next-line no-undef
const usersIds = defineModel<string[]>({ default: [] });
defineProps<{
max?: number
}>();
}>()
const usersIds = defineModel<string[]>({ default: [] })
const getUsers = useTwitchGetUsers({
ids: usersIds,
});
})
const userName = ref('');
const userNameDebounced = refDebounced(userName, 500);
const userName = ref('')
const userNameDebounced = refDebounced(userName, 500)
const searchParams = computed<TwitchSearchChannelsRequest>(() => ({
query: userNameDebounced.value,
twirOnly: false,
}));
const twitchSearch = useTwitchSearchChannels(searchParams);
}))
const twitchSearch = useTwitchSearchChannels(searchParams)
function mapOptions(users: (TwitchUser | Channel)[] ) {
function mapOptions(users: (TwitchUser | Channel)[]) {
return users.map((user) => ({
label: resolveUserName(user.login, user.displayName),
value: user.id,
profileImageUrl: user.profileImageUrl,
}));
}))
}
const options = computed(() => {
const searchUsers = twitchSearch.data.value?.channels ?? [];
const initialUsers = getUsers.data.value?.users ?? [];
const searchUsers = twitchSearch.data.value?.channels ?? []
const initialUsers = getUsers.data.value?.users ?? []
return [
...mapOptions(searchUsers)
.filter((channel) => !initialUsers.find((user) => user.id === channel.value)),
...mapOptions(initialUsers),
];
});
]
})
function handleSearch(query: string) {
userName.value = query;
userName.value = query
}
type Option = {
label: string;
value: string;
profileImageUrl: string;
};
interface Option {
label: string
value: string
profileImageUrl: string
}
const renderMultipleSelectTag = ({ option, handleClose }: {
option: Option;
handleClose: () => void;
}) => {
function renderMultipleSelectTag({ option, handleClose }: {
option: Option
handleClose: () => void
}) {
return h(
NTag,
{
class: 'pr-1.5 pl-1',
round: true,
closable: true,
onClose: (e) => {
e.stopPropagation();
handleClose();
e.stopPropagation()
handleClose()
},
},
{
Expand All @@ -87,10 +85,10 @@ const renderMultipleSelectTag = ({ option, handleClose }: {
],
),
},
);
};
)
}
const renderLabel = (option: Option) => {
function renderLabel(option: Option) {
return h(
'div',
{ class: 'flex items-center' },
Expand All @@ -106,12 +104,12 @@ const renderLabel = (option: Option) => {
[h('div', null, option.label)],
),
],
);
};
)
}
</script>

<template>
<n-select
<NSelect
v-model:value="usersIds"
multiple
:filterable="max ? usersIds.length !== max : true"
Expand Down
Loading

0 comments on commit 6733a4e

Please sign in to comment.