Menu

Change language

GitHub repo

500 Server Error

blocklayout

Full-page 500 with an isometric crashed-server illustration, HTTP code, headline, and Try again + Back to home + Contact support. Prop-driven: pass title, description, image, action labels, hrefs, and layout (`page` | `contained`). Pass `image=""` to hide the art.

Installation

$npx uipkge-ng@latest add error-500
Manifest:https://uipkge.dev/r/angular/error-500.jsonInstalls to:src/app/components/blocks/error-500/

Variants

Loading interactive previews…

npm dependencies

Includes

Files installed (2)

  • src/app/components/blocks/error-500/error-500.component.ts6.5 kB
    // Full-page 500 with an isometric crashed-server illustration slot, HTTP code, headline,
    // and Try again + Back to home + Contact support actions. Port of the React block 1:1.
    import { ChangeDetectionStrategy, Component, EventEmitter, Input, Output } from '@angular/core'
    import { LifeBuoy, LucideAngularModule, RefreshCcw } from 'lucide-angular'
    import { cn } from '@/lib/utils'
    import { UiButtonComponent } from '@/ui/button/button.component'
    
    @Component({
      changeDetection: ChangeDetectionStrategy.Eager,
      selector: 'ui-error-500, [ui-error-500]',
      standalone: true,
      // React renders the root <section> itself: the host stays out of layout and `class` goes to the root.
      host: { '[attr.class]': '"contents"' },
      imports: [LucideAngularModule, UiButtonComponent],
      template: `
        <section data-slot="error-500" [attr.data-layout]="layout" [class]="rootClass">
          <div class="mx-auto grid w-full max-w-5xl items-center gap-10 lg:grid-cols-2 lg:gap-16">
            <div>
              @if (codeText) {
                <p class="text-muted-foreground text-sm font-medium">HTTP {{ codeText }}</p>
              }
              @if (codeText) {
                <p class="mt-3 text-6xl font-semibold tracking-tighter sm:text-7xl">{{ codeText }}</p>
              }
              <h1 class="mt-3 text-2xl font-semibold tracking-tight sm:text-3xl">{{ titleText }}</h1>
              @if (descriptionText) {
                <p class="text-muted-foreground mt-4 max-w-md text-base">{{ descriptionText }}</p>
              }
              @if (showPrimary || showSecondary || showTertiary) {
                <div class="mt-8 flex flex-wrap items-center gap-3">
                  @if (showPrimary) {
                    @if (primaryHref) {
                      <a ui-button size="lg" [href]="primaryHref" (click)="primary.emit()">
                        <lucide-icon [img]="RefreshCcw" />
                        {{ primaryText }}
                      </a>
                    } @else {
                      <button ui-button size="lg" [disabled]="primaryDisabled" (click)="primary.emit()">
                        <lucide-icon [img]="RefreshCcw" />
                        {{ primaryText }}
                      </button>
                    }
                  }
                  @if (showSecondary) {
                    @if (secondaryHref) {
                      <a ui-button size="lg" variant="outline" [href]="secondaryHref" (click)="secondary.emit()">
                        {{ secondaryText }}
                      </a>
                    } @else {
                      <button
                        ui-button
                        size="lg"
                        variant="outline"
                        [disabled]="secondaryDisabled"
                        (click)="secondary.emit()"
                      >
                        {{ secondaryText }}
                      </button>
                    }
                  }
                  @if (showTertiary) {
                    @if (tertiaryHref) {
                      <a ui-button size="lg" variant="ghost" [href]="tertiaryHref" (click)="tertiary.emit()">
                        {{ tertiaryText }}
                        <lucide-icon [img]="LifeBuoy" />
                      </a>
                    } @else {
                      <button ui-button size="lg" variant="ghost" [disabled]="tertiaryDisabled" (click)="tertiary.emit()">
                        {{ tertiaryText }}
                        <lucide-icon [img]="LifeBuoy" />
                      </button>
                    }
                  }
                </div>
              }
            </div>
            @if (image) {
              <div class="border-border bg-muted overflow-hidden rounded-xl border">
                <img [src]="image" [alt]="imageAlt" class="aspect-[4/3] w-full object-cover" />
              </div>
            } @else if (image === undefined) {
              <div class="border-border bg-muted/40 overflow-hidden rounded-xl border font-mono" aria-hidden="true">
                <div class="border-border text-muted-foreground truncate border-b px-3 py-2 text-xs">
                  POST /api/v1/invoices
                </div>
                <div class="space-y-1 p-4 text-xs leading-relaxed">
                  <p>
                    <span class="text-muted-foreground">HTTP/1.1</span>
                    {{ ' ' }}
                    <span class="text-destructive">{{ codeText || '500' }} Internal Server Error</span>
                  </p>
                  <p class="text-muted-foreground">error: ECONNRESET</p>
                  <p class="text-muted-foreground">at: Worker.run:142</p>
                  <p class="text-muted-foreground">x-request-id: req_8f3c2a</p>
                </div>
              </div>
            }
          </div>
        </section>
      `,
    })
    export class UiError500Component {
      protected readonly LifeBuoy = LifeBuoy
      protected readonly RefreshCcw = RefreshCcw
    
      @Input() code?: string
      @Input() title?: string
      @Input() description?: string
      /** Replaces the HTTP response panel. Pass `''` to hide art entirely. */
      @Input() image: string | undefined = '/illustrations/error-500.jpg'
      @Input() imageAlt = ''
      @Input() primaryLabel?: string
      @Input() secondaryLabel?: string
      @Input() tertiaryLabel?: string
      @Input() primaryHref?: string
      @Input() secondaryHref?: string
      @Input() tertiaryHref?: string
      @Input() showPrimary = true
      @Input() showSecondary = true
      @Input() showTertiary = true
      @Input() primaryDisabled = false
      @Input() secondaryDisabled = false
      @Input() tertiaryDisabled = false
      @Output() primary = new EventEmitter<void>()
      @Output() secondary = new EventEmitter<void>()
      @Output() tertiary = new EventEmitter<void>()
      /** `'page'` fills the viewport; `'contained'` fits a preview or nested panel. */
      @Input() layout: 'page' | 'contained' = 'page'
      @Input('class') className?: string
    
      get codeText(): string {
        return this.code === undefined ? '500' : this.code
      }
    
      get titleText(): string {
        return this.title === undefined ? 'Something went wrong' : this.title
      }
    
      get descriptionText(): string {
        return this.description === undefined
          ? 'We hit an unexpected error on our side. Try again in a moment — if it keeps happening, our team is on it.'
          : this.description
      }
    
      get primaryText(): string {
        return this.primaryLabel === undefined ? 'Try again' : this.primaryLabel
      }
    
      get secondaryText(): string {
        return this.secondaryLabel === undefined ? 'Back to home' : this.secondaryLabel
      }
    
      get tertiaryText(): string {
        return this.tertiaryLabel === undefined ? 'Contact support' : this.tertiaryLabel
      }
    
      get rootClass(): string {
        return cn(
          'bg-background relative flex items-center overflow-hidden px-6 py-16',
          this.layout === 'page' ? 'min-h-svh' : 'min-h-[28rem]',
          this.className,
        )
      }
    }
    
  • src/app/components/blocks/error-500/index.ts0.1 kB

Raw manifest:https://uipkge.dev/r/angular/error-500.json