-
-
Notifications
You must be signed in to change notification settings - Fork 37
/
error.vue
215 lines (191 loc) · 7.17 KB
/
error.vue
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
<script setup lang="ts">
import { IconMoodSad as IconCrash, IconRoadSign as IconLost, IconMoodConfuzed as IconOtherError, IconRefreshAlert as IconReset, IconRefresh as IconReload, IconHome as IconHome, IconBrandGithub as IconGithub, IconBrandTwitter as IconTwitter, IconMessages as IconDiscussion, IconBug as IconShowError } from '@tabler/icons-vue'
import { useI18n } from 'vue-i18n'
import type { Component, ComputedRef } from 'vue'
import { ActionType } from './components/error/types'
import UiButton from './components/base/uiButton.vue'
import { ButtonImportance, ButtonTheme } from './components/base/types/button'
import ActionBar from '~~/components/error/errorActionBar.vue'
import { useSettings } from '~~/stores/settings'
const { t } = useI18n()
const route = useRoute()
const settingsStore = useSettings()
enum ErrorType {
Crash = 'crash',
NotFound = 'notfound',
Other = 'other'
}
enum Action {
Reload = 'reload',
Reset = 'reset',
Home = 'home',
GithubIssue = 'githubIssue',
GithubDiscussion = 'githubDiscussion',
Twitter = 'twitter'
}
/** Action button presets */
const actions: Record<ErrorType, Record<Action, ActionType>> = {
crash: {
reload: ActionType.RECOMMEND,
reset: ActionType.PRIMARY,
home: ActionType.PRIMARY,
githubIssue: ActionType.SECONDARY,
githubDiscussion: ActionType.SECONDARY,
twitter: ActionType.SECONDARY
},
notfound: {
home: ActionType.RECOMMEND,
githubDiscussion: ActionType.SECONDARY,
twitter: ActionType.SECONDARY,
githubIssue: ActionType.HIDE,
reload: ActionType.HIDE,
reset: ActionType.HIDE
},
other: {
reload: ActionType.RECOMMEND,
home: ActionType.PRIMARY,
reset: ActionType.PRIMARY,
githubIssue: ActionType.SECONDARY,
githubDiscussion: ActionType.SECONDARY,
twitter: ActionType.SECONDARY
}
}
const props = defineProps({
error: {
type: Object,
default: null
}
})
const state = reactive({
showError: false
})
/** Determines what type of error happened */
const currentErrorType: ComputedRef<ErrorType> = computed(() => {
if (Number.parseInt(props.error.statusCode) >= 500) { return ErrorType.Crash }
if (Number.parseInt(props.error.statusCode) === 404) { return ErrorType.NotFound }
return ErrorType.Other
})
const Icons: Record<ErrorType, Component> = {
crash: IconCrash,
notfound: IconLost,
other: IconOtherError
}
const errorHeading = computed(() => t('errorpage.title.' + currentErrorType.value))
/** The title and icon to show on the error page.
* For example on a 404 error it would show "Looks like you're lost",
* but on an app error (eg. "crash" because of a store error) it would show
* "Oops, the app crashed". Can return the icon as well.
*/
useHead({
title: errorHeading.value
})
/** Returns actions to show or recommend based on the error.
* For example if it's a 404 error, go to home or the
* discussion button is recommended but the reset button is hidden.
*/
const recommendedActions = computed(() => {
return actions[currentErrorType.value]
})
const fullError = computed(() => {
return Object.assign({}, props.error, { route })
})
/// Ask settings to reset and navigate back to the home page
const actionReset = () => {
settingsStore.setReset(true)
location.assign('/')
}
/// Reload the current page
const actionReload = () => {
location.reload()
}
interface ButtonStyleMapping {
theme: ButtonTheme,
importance: ButtonImportance
}
const actionTypeToButtonStyle = (action: ActionType): ButtonStyleMapping => {
switch (action) {
case ActionType.RECOMMEND:
return {
theme: ButtonTheme.Primary,
importance: ButtonImportance.Filled
}
case ActionType.PRIMARY:
return {
theme: ButtonTheme.Primary,
importance: ButtonImportance.Outline
}
case ActionType.SECONDARY:
return {
theme: ButtonTheme.Secondary,
importance: ButtonImportance.Outline
}
default: {
return {
theme: ButtonTheme.Neutral,
importance: ButtonImportance.Outline
}
}
}
}
</script>
<template>
<div class="flex flex-col items-center justify-center w-screen min-h-screen p-6 text-surface-ondark bg-surface-dark dark">
<div>
<component :is="Icons[currentErrorType]" size="128" stroke-width="1.25" />
</div>
<h1 class="mt-2 text-5xl font-bold tracking-tighter text-center uppercase" v-text="errorHeading" />
<!-- Error description -->
<div class="max-w-screen-lg mt-8 border-2 border-gray-300 rounded-lg">
<transition name="showerror-transition" mode="out-in">
<div v-if="!state.showError" class="flex flex-row items-center p-4 space-x-4 text-surface-ondark transition bg-surface-darkvariant cursor-pointer" role="button" @click="state.showError = true">
<IconShowError size="42" />
<div>
<div class="font-bold" v-text="$t('errorpage.showError.main')" />
<div v-text="$t('errorpage.showError.sub')" />
</div>
</div>
<div v-else-if="state.showError" class="p-4 overflow-y-scroll max-h-56 max-w-full break-before-all">
<pre v-text="fullError" />
</div>
</transition>
</div>
<!-- Recommended actions -->
<ActionBar class="mt-8">
<UiButton :data-row="recommendedActions.reset" v-bind="actionTypeToButtonStyle(recommendedActions.reset)" @click="actionReset">
<IconReset class="mr-1" />
<div v-text="$t('errorpage.action.reset')" />
</UiButton>
<UiButton :data-row="recommendedActions.reload" v-bind="actionTypeToButtonStyle(recommendedActions.reload)" @click="actionReload">
<IconReload class="mr-1" />
<div v-text="$t('errorpage.action.reload')" />
</UiButton>
<UiButton link :data-row="recommendedActions.home" v-bind="actionTypeToButtonStyle(recommendedActions.home)" href="/">
<IconHome class="mr-1" />
<div v-text="$t('errorpage.action.home')" />
</UiButton>
<UiButton link :data-row="recommendedActions.githubIssue" v-bind="actionTypeToButtonStyle(recommendedActions.githubIssue)" href="https://github.com/Hanziness/FocusTide/issues?utm_source=AnotherPomodor&utm_medium=web&utm_content=error">
<IconGithub class="mr-1" />
<div v-text="$t('errorpage.action.githubIssue')" />
</UiButton>
<UiButton link :data-row="recommendedActions.githubDiscussion" v-bind="actionTypeToButtonStyle(recommendedActions.githubDiscussion)" href="https://github.com/Hanziness/FocusTide/discussions?utm_source=AnotherPomodor&utm_medium=web&utm_content=error">
<IconDiscussion class="mr-1" />
<div v-text="$t('errorpage.action.githubDiscussion')" />
</UiButton>
<UiButton link :data-row="recommendedActions.twitter" v-bind="actionTypeToButtonStyle(recommendedActions.home)" href="https://twitter.com/FocusTide?utm_source=AnotherPomodor&utm_medium=web&utm_content=error">
<IconTwitter class="mr-1" />
<div v-text="$t('errorpage.action.twitter')" />
</UiButton>
</ActionBar>
</div>
</template>
<style lang="scss" scoped>
.showerror-transition-enter-active {
@apply transition-all duration-700;
}
.showerror-transition-enter {
clip-path: circle(0%);
}
.showerror-transition-enter-to {
clip-path: circle(75%);
}
</style>