Skip to content

Commit

Permalink
Refactored emoji rating bar
Browse files Browse the repository at this point in the history
  • Loading branch information
veliu committed Oct 23, 2024
1 parent ba59903 commit 60dced0
Show file tree
Hide file tree
Showing 4 changed files with 68 additions and 89 deletions.
73 changes: 42 additions & 31 deletions components/Food/EmojiRatingBar.vue
Original file line number Diff line number Diff line change
@@ -1,46 +1,57 @@
<script setup lang="ts">
withDefaults(
defineProps<{
modelValue: number;
emojiSize?: string;
}>(),
{
modelValue: 0,
emojiSize: "1.7em",
},
);
const emit = defineEmits(["update:modelValue"]);
const handleInput = (rating: number) => {
emit("update:modelValue", rating);
};
import type { Food, FoodRating } from "~/types/ApiTypes";
const props = defineProps<{
food: Food;
}>();
const food = toRef(props.food);
const emit = defineEmits(["update:rating"]);
const { ratings, updateRating, personalRating } = useFoodRating(food);
const emojiRatings = {
const emojis = {
1: { value: 1, emoji: "" },
2: { value: 2, emoji: "👍" },
3: { value: 3, emoji: "😐" },
4: { value: 4, emoji: "🙄" },
5: { value: 5, emoji: "😫" },
6: { value: 6, emoji: "🤢" },
};
const personalRatingValue = computed(() => personalRating.value?.rating);
const countRatingsFor = (rating: number): number => {
let filtered: FoodRating[];
filtered = ratings.value.filter((r) => r.rating === rating);
return filtered.length;
};
const invokeUpdateRating = async (value: number) => {
await updateRating(value);
emit("update:rating", value);
};
</script>

<template>
<div class="flex flex-row justify-between">
<UButton
v-for="emojiRating in emojiRatings"
:key="emojiRating.value"
variant="link"
@click="handleInput(emojiRating.value)"
<div class="flex flex-row justify-evenly">
<div
v-for="emoji in emojis"
:key="emoji.value"
class="py-1 px-2 border border-1 rounded-lg flex flex-row gap-2 border-gray-600 hover:border-primary"
:class="{
'border-primary hover:border-primary-300':
personalRatingValue === emoji.value,
}"
>
<Twemoji
:emoji="emojiRating.emoji"
:size="emojiSize"
:class="
emojiRating.value !== modelValue ? 'brightness-50' : 'animate-bounce'
"
/>
</UButton>
<button
class="flex flex-row justify-center items-center gap-1"
@click="invokeUpdateRating(emoji.value)"
>
<Twemoji size="1.2em" :emoji="emoji.emoji" />
<span class="font-bold">{{ countRatingsFor(emoji.value) }}</span>
</button>
</div>
</div>
</template>
23 changes: 7 additions & 16 deletions components/Food/FoodCard.vue
Original file line number Diff line number Diff line change
@@ -1,8 +1,7 @@
<script setup lang="ts">
import type { Food, UpsertFoodRatingRequest } from "~/types/ApiTypes";
import EmojiRatingBar from "~/components/Food/EmojiRatingBar.vue";
import EmojiRating from "~/components/Food/EmojiRating.vue";
import type { Food } from "~/types/ApiTypes";
import { useFoodRating } from "~/composables/useFoodRating";
import EmojiRatingBar from "~/components/Food/EmojiRatingBar.vue";
const props = defineProps<{
food: Food;
Expand All @@ -23,12 +22,6 @@ onMounted(() => {

<template>
<UCard>
<template #header>
<div class="flex flex-row justify-between">
<span>Average Rating</span>
<EmojiRating :rating-value="food.averageRating" />
</div>
</template>
<div class="aspect-h-2 aspect-w-3 sm:aspect-none hover:opacity-75 sm:h-96">
<NuxtLink :to="'/food/' + food.id">
<NuxtImg
Expand All @@ -39,18 +32,16 @@ onMounted(() => {
/>
</NuxtLink>
</div>
<div class="flex flex-1 flex-col space-y-2 py-4">
<div class="flex flex-1 flex-col mt-4">
<h3 class="text-xl font-medium">
{{ food.name }}
</h3>
<div class="flex flex-1 flex-col justify-end">
<p class="text-sm italic text-gray-500">
{{ food.description }}
</p>
</div>
<p class="text-sm italic text-gray-500">
{{ food.description }}
</p>
</div>
<template #footer>
<EmojiRatingBar v-model="selectedRating" />
<EmojiRatingBar class="mt-auto" :food="food" />
</template>
</UCard>
</template>
33 changes: 5 additions & 28 deletions components/Food/FoodDetail.vue
Original file line number Diff line number Diff line change
@@ -1,11 +1,10 @@
<script setup lang="ts">
import { Tab, TabGroup, TabList, TabPanel, TabPanels } from "@headlessui/vue";
import type { Food, UpdateFoodRequest } from "~/types/ApiTypes";
import EmojiRating from "./EmojiRating.vue";
import EmojiRatingBar from "./EmojiRatingBar.vue";
import { useSessionStore } from "~/store/session.store";
import { useFoodRating } from "~/composables/useFoodRating";
import { useFoodComment } from "~/composables/useFoodComment";
import EmojiRatingBar from "~/components/Food/EmojiRatingBar.vue";
import EmojiRating from "~/components/Food/EmojiRating.vue";
const props = defineProps<{
food: Food;
Expand All @@ -17,16 +16,12 @@ const foodId = computed(() => food.value.id);
const { user } = useSessionStore();
const { createdBy, deleteProduct, updateFood } = useFood(food);
const { ratings, personalRating, updateRating } = useFoodRating(food);
const { comments, addComment } = useFoodComment(food);
const { getFood } = useSearch();
const isLoading = ref(false);
const updateMode = ref(false);
const selectedRating: Ref<number> = ref(0);
const name = ref(food.value.name);
const description = ref(food.value.description ?? undefined);
Expand All @@ -35,10 +30,9 @@ const updateRequest = computed<UpdateFoodRequest>(() => ({
description: description.value === "" ? undefined : description.value,
}));
watch(selectedRating, async () => {
await updateRating(selectedRating);
const invokeRefreshFood = async () => {
food.value = await getFood(foodId);
});
};
const newComment = ref<string>("");
Expand All @@ -51,10 +45,6 @@ const invokeUpdateFood = async () => {
await updateFood(updateRequest);
updateMode.value = false;
};
onMounted(() => {
selectedRating.value = personalRating.value?.rating ?? 0;
});
</script>

<template>
Expand Down Expand Up @@ -161,20 +151,7 @@ onMounted(() => {

<section id="product-rating">
<div class="my-6">
<h3 class="text-2xl">Personal rating</h3>
<EmojiRatingBar v-model="selectedRating" />
</div>
<div v-if="ratings.length > 1" class="my-4">
<h3 class="font-bold text-xl mb-2">All other ratings</h3>
<div v-for="rating in ratings" :key="rating.id">
<div
v-if="user?.id !== rating.createdBy.id"
class="flex flex-row gap-2 content-center"
>
<span class="content-center">{{ rating.createdBy.email }}</span>
<EmojiRating :rating-value="rating.rating" />
</div>
</div>
<EmojiRatingBar :food="food" @update:rating="invokeRefreshFood" />
</div>
</section>
</section>
Expand Down
28 changes: 14 additions & 14 deletions composables/useFoodRating.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,12 +5,12 @@ import { useSessionStore } from "~/store/session.store";
export type useFoodRatingReturn = {
ratings: ComputedRef<FoodRating[]>;
personalRating: ComputedRef<FoodRating | null>;
updateRating(rating: Ref<number>): Promise<FoodRating | undefined>;
updateRating(
rating: MaybeRefOrGetter<number>,
): Promise<FoodRating | undefined>;
};

export function useFoodRating(food: Ref<Food>): useFoodRatingReturn {
const _latestRating = ref<FoodRating | undefined>(undefined);

const { $apiFetcher } = useNuxtApp();
const { token } = storeToRefs(useSessionStore());
const toast = useToast();
Expand All @@ -29,12 +29,6 @@ export function useFoodRating(food: Ref<Food>): useFoodRatingReturn {
});
}

const { data: _ratings } = useAsyncData<FoodRatingCollection>(
"fetch-food-rating" + food.value.id,
() => fetchRatings(food.value.id),
{ watch: [food, _latestRating] },
);

const fetchPersonalRating = async (foodId: string): Promise<FoodRating> => {
return $apiFetcher<FoodRating>("/food-rating/my/" + foodId, {
method: "GET",
Expand All @@ -48,6 +42,12 @@ export function useFoodRating(food: Ref<Food>): useFoodRatingReturn {
{ watch: [food] },
);

const { data: _ratings } = useAsyncData<FoodRatingCollection>(
"fetch-food-rating" + food.value.id,
() => fetchRatings(food.value.id),
{ watch: [food, _personalRating] },
);

async function upsertPersonalRating(
foodId: string,
rating: number,
Expand All @@ -63,13 +63,15 @@ export function useFoodRating(food: Ref<Food>): useFoodRatingReturn {
}

async function updateRating(
rating: Ref<number>,
rating: MaybeRefOrGetter<number>,
): Promise<FoodRating | undefined> {
const _rating = toRef(rating);
try {
_latestRating.value = await upsertPersonalRating(
_personalRating.value = await upsertPersonalRating(
food.value.id,
rating.value,
_rating.value,
);
return _personalRating.value as FoodRating;
} catch (error) {
toast.add({
id: "update-rating-failed",
Expand All @@ -79,8 +81,6 @@ export function useFoodRating(food: Ref<Food>): useFoodRatingReturn {
});
console.error(error);
}

return _latestRating.value;
}

return {
Expand Down

0 comments on commit 60dced0

Please sign in to comment.