Newer
Older
smart-home-server / webclient-vue / src / components / feedback / AppErrorState.vue
<template>
  <GnAlert variant="danger" role="alert">
    <strong>{{ title }}</strong>
    <p v-if="displayMessage">{{ displayMessage }}</p>

    <div v-if="error" class="error-meta">
      <GnBadge v-if="error.type" variant="secondary">{{ error.type }}</GnBadge>
      <GnBadge v-if="error.statusCode" variant="secondary">HTTP {{ error.statusCode }}</GnBadge>
      <GnBadge v-if="error.errorAlias" variant="warning">{{ error.errorAlias }}</GnBadge>
    </div>

    <div class="error-actions">
      <GnCopyButton
        v-if="error"
        :text="formattedDetails"
        label="Copy error details"
        size="sm"
      />
      <GnButton v-if="retry" variant="danger" @click="retry">Retry</GnButton>
    </div>
  </GnAlert>
</template>

<script setup>
import { computed } from "vue";
import { GnAlert, GnBadge, GnButton, GnCopyButton } from "gnexus-ui-kit/vue";

const props = defineProps({
  title: {
    type: String,
    default: "Request failed",
  },
  message: {
    type: String,
    default: "",
  },
  retry: {
    type: Function,
    default: null,
  },
  error: {
    type: Object,
    default: null,
  },
});

const displayMessage = computed(() => props.message || props.error?.message || "");

const formattedDetails = computed(() => {
  if (!props.error) return "";
  try {
    return JSON.stringify(props.error, null, 2);
  } catch {
    return String(props.error);
  }
});
</script>

<style scoped>
.error-meta {
  display: flex;
  flex-wrap: wrap;
  gap: 6px;
  margin-top: 8px;
}

.error-actions {
  display: flex;
  flex-wrap: wrap;
  gap: 8px;
  align-items: center;
  margin-top: 12px;
}
</style>