Skip to content

Commit

Permalink
feat: add error handling
Browse files Browse the repository at this point in the history
  • Loading branch information
alexander-heimbuch committed Sep 4, 2024
1 parent 5e2da4b commit 8c3a0f2
Show file tree
Hide file tree
Showing 15 changed files with 318 additions and 43 deletions.
48 changes: 48 additions & 0 deletions client/src/features/notifications/Notifications.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
<template>
<!-- Global notification live region, render this permanently at the end of the document -->
<div
aria-live="assertive"
class="pointer-events-none fixed inset-0 flex items-end px-4 py-6 sm:items-start sm:p-6 z-50"
>
<div class="flex w-full flex-col items-center space-y-4 sm:items-end">
<!-- Notification panel, dynamically insert this into the live region when it needs to be displayed -->
<transition
enter-active-class="transform ease-out duration-300 transition"
enter-from-class="translate-y-2 opacity-0 sm:translate-y-0 sm:translate-x-2"
enter-to-class="translate-y-0 opacity-100 sm:translate-x-0"
leave-active-class="transition ease-in duration-100"
leave-from-class="opacity-100"
leave-to-class="opacity-0"
v-for="(notification, index) in state.notifications"
>
<NotificationComponent
v-if="notification.visible"
:type="notification.type"
:title="notification.title"
:details="notification.details"
@close="closeNotification(index)"
/>
</transition>
</div>
</div>
</template>

<script setup lang="ts">
import { type Action } from 'redux';
import { injectStore, mapState } from 'redux-vuex';
import { actions, selectors } from '../../store';
import { type Notification } from '../../types/notification.types';
import NotificationComponent from './components/Notification.vue';
const store = injectStore();
const state = mapState<{
notifications: Notification[]
}>({
notifications: selectors.notifications
});
const closeNotification = (index: number) => {
store.dispatch(actions.notifications.hide(index) as Action);
};
</script>
74 changes: 74 additions & 0 deletions client/src/features/notifications/components/Notification.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
<template>
<div
class="pointer-events-auto w-full max-w-sm overflow-hidden rounded-lg bg-white shadow-lg ring-1 ring-black ring-opacity-5"
>
<div class="p-4">
<div class="flex items-start">
<div class="flex-shrink-0">
<InformationCircleIcon
v-if="type === 'info'"
class="h-6 w-6"
:class="textColor"
aria-hidden="true"
/>
<ExclamationCircleIcon
v-if="type === 'error'"
class="h-6 w-6"
:class="textColor"
aria-hidden="true"
/>
<ExclamationCircleIcon
v-if="type === 'warning'"
class="h-6 w-6"
:class="textColor"
aria-hidden="true"
/>
<CheckCircleIcon
v-if="type === 'success'"
class="h-6 w-6"
:class="textColor"
aria-hidden="true"
/>
</div>
<div class="ml-3 w-0 flex-1 pt-0.5">
<p class="text-sm font-medium" :class="textColor">{{ title }}</p>
<p class="mt-1 text-sm text-gray-500" v-if="details">
{{ details }}
</p>
</div>
<div class="ml-4 flex flex-shrink-0">
<button
type="button"
@click="emit('close')"
class="inline-flex rounded-md bg-white text-gray-400 hover:text-gray-500 focus:outline-none focus:ring-2 focus:ring-indigo-500 focus:ring-offset-2"
>
<span class="sr-only">Close</span>
<XMarkIcon class="h-5 w-5" aria-hidden="true" />
</button>
</div>
</div>
</div>
</div>
</template>

<script setup lang="ts">
import { InformationCircleIcon, ExclamationCircleIcon, CheckCircleIcon } from '@heroicons/vue/24/outline';
import { XMarkIcon } from '@heroicons/vue/20/solid';
import { type Notification } from '../../../types/notification.types';
import { computed } from 'vue';
const props = defineProps<{ type: Notification['type']; title: string; details?: string }>();
const emit = defineEmits(['close']);
const textColor = computed(() => {
switch (props.type) {
case 'info':
return 'text-gray-900';
case 'error':
return 'text-red-600';
case 'warning':
return 'text-yellow-600';
case 'success':
return 'text-green-600';
}
});
</script>
16 changes: 16 additions & 0 deletions client/src/i18n/de.ts
Original file line number Diff line number Diff line change
Expand Up @@ -166,6 +166,22 @@ export default {
playButton: 'Staffel 2, Ep 1',
description:
'[Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet.]'
},
error: {
podcast: {
metadata: {
title: 'Anfrage fehlgeschlagen',
details: 'Fehler beim abfragen der Podcast Metadaten'
},
episodes: {
title: 'Anfrage fehlgeschlagen',
details: 'Fehler beim abfragen der Podcast Episoden'
},
feedUrl: {
title: 'Anfrage fehlgeschlagen',
details: 'Fehler beim Abfragen der Feed Url'
}
}
}
},
select: {
Expand Down
16 changes: 16 additions & 0 deletions client/src/i18n/en.ts
Original file line number Diff line number Diff line change
Expand Up @@ -167,6 +167,22 @@ export default {
playButton: 'Season 2, Ep 1',
description:
'[Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet.]'
},
error: {
podcast: {
metadata: {
title: 'Request failed',
details: 'Error while fetching podcast metadata'
},
episodes: {
title: 'Request failed',
details: 'Error while fetching podcast episodes'
},
feedUrl: {
title: 'Request failed',
details: 'Error while fetching feed url'
}
}
}
},
select: {
Expand Down
8 changes: 5 additions & 3 deletions client/src/lib/request.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,14 +15,16 @@ const headers = (): HeadersInit => {
...(site ? {'Wordpress-Site': site}: {})
};
};
const checkResponse = (response: Response): Response => {
const checkResponse = async (response: Response): Promise<Response> => {
if (!response.ok) {
throw new Error('API call failed!')
const error = await response.text();
throw new Error(error);
}

return response;
}

const parseResponse = <T>(response: Response): Promise<T> => { return response.json(); }
const parseResponse = <T>(response: Response): Promise<T> => response.json();

export const origin = (path: string): string => {
const url = new URL(document.baseURI).origin;
Expand Down
4 changes: 3 additions & 1 deletion client/src/pages/onboarding/Onboarding.vue
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
<template>
<div ref="viewPort" class="relative h-full flex flex-col" :data-test="`step-${state.current.name}`">
<Steps class="fixed top-0 w-full z-50" :steps="state.steps" v-if="stepsVisible"></Steps>
<NotificationsFeature :class="{ 'mt-[80px]': stepsVisible }" />
<div
id="content"
class="pt-4 pb-[50px] sm:px-6 xl:pl-6 overflow-y-auto"
class="pt-4 pb-[50px] sm:px-6 xl:pl-6 overflow-y-auto relative"
:class="{ 'pt-[80px]': stepsVisible }"

>
Expand Down Expand Up @@ -33,6 +34,7 @@ import { useI18n } from 'vue-i18n';
import { selectors } from '../../store';
import Steps from './components/Steps.vue';
import PodloveButton from '../../components/button/Button.vue';
import NotificationsFeature from '../../features/notifications/Notifications.vue';
import SetupType from './steps/SetupType.vue';
Expand Down
19 changes: 11 additions & 8 deletions client/src/pages/onboarding/steps/import/Feed.vue
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,10 @@
<p class="my-2 text-base leading-6 text-gray-600">
{{ t('onboarding.steps.import-feed.description') }}
</p>
<p class="my-2 text-base leading-6 text-gray-600" v-html="$t('onboarding.steps.import-feed.help')">
</p>
<p
class="my-2 text-base leading-6 text-gray-600"
v-html="t('onboarding.steps.import-feed.help')"
></p>
<FeedInput class="mb-5"></FeedInput>
<div v-if="state.feedStatus === 'valid'">
<div class="rounded-md bg-green-50 p-4">
Expand All @@ -22,9 +24,7 @@
{{ t('onboarding.steps.import-feed.success-head') }}
</h3>
<div class="mt-2 text-base leading-6 text-green-700">
<p>
{{ t('onboarding.steps.import-feed.success-info') }}
</p>
<p v-html="t('onboarding.steps.import-feed.success-info')"></p>
</div>
</div>
</div>
Expand Down Expand Up @@ -58,15 +58,19 @@

<!-- Right column area -->
<div class="px-4 py-6 sm:px-6 w-full flex justify-center items-center">
<img :src="moveIconUrl" alt="" class="aspect-[16/9] w-96 rounded-2xl bg-gray-100 object-cover sm:aspect-[2/1] lg:aspect-[3/2]" />
<img
:src="moveIconUrl"
alt=""
class="aspect-[16/9] w-96 rounded-2xl bg-gray-100 object-cover sm:aspect-[2/1] lg:aspect-[3/2]"
/>
</div>
</div>
</template>

<script setup lang="ts">
import { mapState } from 'redux-vuex';
import { useI18n } from 'vue-i18n';
import { XCircleIcon, CheckCircleIcon } from '@heroicons/vue/20/solid'
import { XCircleIcon, CheckCircleIcon } from '@heroicons/vue/20/solid';
import FeedInput from '../../components/FeedInput.vue';
import moveIconUrl from '../../../../assets/feed-import.svg';
Expand All @@ -78,7 +82,6 @@ const { t } = useI18n();
const state = mapState({
feedStatus: selectors.feed.feedStatus
});
</script>

<style>
Expand Down
59 changes: 44 additions & 15 deletions client/src/sagas/import.saga.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,11 @@ import * as request from '../lib/request';
import { findCategories } from '../helper/categories';
import { LanguageLocales } from '../types/locales.types';
import { type Episode, type EpisodeDetailsPayload } from '../types/episode.types';
import { savePodcastMetadata } from './helpers/podcast';
import { setOnboardingPodcastSettings } from './helpers/settings';
import { savePodcastMetadata } from './helpers/podcast';
import { i18n } from '../i18n';

const { t } = i18n.global;

function* validateFeedUrl({ payload }: Action<validateFeedUrlPayload>) {
const feed = payload.trim();
Expand All @@ -32,15 +35,26 @@ function* validateFeedUrl({ payload }: Action<validateFeedUrlPayload>) {

function* fetchPodcastMetaData(): any {
const feedUrl: string = yield select(selectors.feed.feedUrl);
let podcast: any;

const response: any = yield request.get(request.origin('api/v1/fetch_feed'), {
params: {
feed_url: feedUrl,
force_refresh: true
}
});
try {
const response: any = yield request.get(request.origin('api/v1/fetch_feed'), {
params: {
feed_url: feedUrl,
force_refresh: true
}
});

const podcast = get(response, ['podcast'], null);
podcast = get(response, ['podcast'], null);
} catch (err) {
podcast = null;
yield put(
actions.notifications.error({
title: t('onboarding.error.podcast.metadata.title'),
details: t('onboarding.error.podcast.metadata.details')
})
);
}

if (!podcast) {
return;
Expand Down Expand Up @@ -75,19 +89,32 @@ function* fetchPodcastMetaData(): any {

function* fetchEpisodes(): any {
const feedUrl: string = yield select(selectors.feed.feedUrl);
const response: any = yield request.get(request.origin('api/v1/fetch_feed'), {
params: { feed_url: feedUrl }
});

const episodes = get(response, ['episodes'], null);
let episodes: null | any[] = null;

if (!episodes) {
try {
const response: any = yield request.get(request.origin('api/v1/fetch_feed'), {
params: { feed_url: feedUrl }
});

episodes = get(response, ['episodes'], null);
} catch (err) {
episodes = null;
yield put(
actions.notifications.error({
title: t('onboarding.error.podcast.episodes.title'),
details: t('onboarding.error.podcast.episodes.details')
})
);
}

if (episodes === null) {
return;
}

yield put(
actions.episodes.addEpisodes(
episodes.map((episode: Episode) => ({
(episodes as any).map((episode: Episode) => ({
title: get(episode, 'title', null),
guid: get(episode, 'guid', null),
pub_date: get(episode, 'pub_date', null),
Expand Down Expand Up @@ -194,7 +221,9 @@ function* importEpisodes(): any {
yield request.post(request.origin('api/v1/import_episode'), {
params: {},
data: episodeDetails
});{}
});
{
}
yield put(actions.episodes.episodeImportFinished(nextEpisode.guid));
} catch (error) {
yield put(actions.episodes.episodeImportFailed(nextEpisode.guid));
Expand Down
Loading

0 comments on commit 8c3a0f2

Please sign in to comment.