diff --git a/components/DotMenu.vue b/components/DotMenu.vue
new file mode 100644
index 0000000000000000000000000000000000000000..24f046e8fcd1aa6c29946c01aeadf14835b56c0a
--- /dev/null
+++ b/components/DotMenu.vue
@@ -0,0 +1,69 @@
+<!--
+Web client of the Sensor Management System software developed within the
+Helmholtz DataHub Initiative by GFZ and UFZ.
+
+Copyright (C) 2020, 2021
+- Nils Brinckmann (GFZ, nils.brinckmann@gfz-potsdam.de)
+- Marc Hanisch (GFZ, marc.hanisch@gfz-potsdam.de)
+- Tobias Kuhnert (UFZ, tobias.kuhnert@ufz.de)
+- Erik Pongratz (UFZ, erik.pongratz@ufz.de)
+- Helmholtz Centre Potsdam - GFZ German Research Centre for
+  Geosciences (GFZ, https://www.gfz-potsdam.de)
+- Helmholtz Centre for Environmental Research GmbH - UFZ
+  (UFZ, https://www.ufz.de)
+
+Parts of this program were developed within the context of the
+following publicly funded projects or measures:
+- Helmholtz Earth and Environment DataHub
+  (https://www.helmholtz.de/en/research/earth_and_environment/initiatives/#h51095)
+
+Licensed under the HEESIL, Version 1.0 or - as soon they will be
+approved by the "Community" - subsequent versions of the HEESIL
+(the "Licence").
+
+You may not use this work except in compliance with the Licence.
+
+You may obtain a copy of the Licence at:
+https://gitext.gfz-potsdam.de/software/heesil
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the Licence is distributed on an "AS IS" basis,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
+implied. See the Licence for the specific language governing
+permissions and limitations under the Licence.
+-->
+<template>
+  <v-menu
+    close-on-click
+    close-on-content-click
+    offset-x
+    left
+    z-index="999"
+  >
+    <template #activator="{ on }">
+      <v-btn
+        data-role="property-menu"
+        icon
+        small
+        v-on="on"
+      >
+        <v-icon
+          dense
+          small
+        >
+          mdi-dots-vertical
+        </v-icon>
+      </v-btn>
+    </template>
+
+    <v-list>
+      <slot name="actions" />
+    </v-list>
+  </v-menu>
+</template>
+<script lang="ts">
+import { Component, Vue } from 'nuxt-property-decorator'
+@Component
+export default class DotMenu extends Vue {
+}
+</script>
diff --git a/components/DotMenuActionCopy.vue b/components/DotMenuActionCopy.vue
new file mode 100644
index 0000000000000000000000000000000000000000..566ed0d1f28d5d78c1e6feb942bbbff668506991
--- /dev/null
+++ b/components/DotMenuActionCopy.vue
@@ -0,0 +1,75 @@
+<!--
+Web client of the Sensor Management System software developed within the
+Helmholtz DataHub Initiative by GFZ and UFZ.
+
+Copyright (C) 2020, 2021
+- Nils Brinckmann (GFZ, nils.brinckmann@gfz-potsdam.de)
+- Marc Hanisch (GFZ, marc.hanisch@gfz-potsdam.de)
+- Tobias Kuhnert (UFZ, tobias.kuhnert@ufz.de)
+- Erik Pongratz (UFZ, erik.pongratz@ufz.de)
+- Helmholtz Centre Potsdam - GFZ German Research Centre for
+  Geosciences (GFZ, https://www.gfz-potsdam.de)
+- Helmholtz Centre for Environmental Research GmbH - UFZ
+  (UFZ, https://www.ufz.de)
+
+Parts of this program were developed within the context of the
+following publicly funded projects or measures:
+- Helmholtz Earth and Environment DataHub
+  (https://www.helmholtz.de/en/research/earth_and_environment/initiatives/#h51095)
+
+Licensed under the HEESIL, Version 1.0 or - as soon they will be
+approved by the "Community" - subsequent versions of the HEESIL
+(the "Licence").
+
+You may not use this work except in compliance with the Licence.
+
+You may obtain a copy of the Licence at:
+https://gitext.gfz-potsdam.de/software/heesil
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the Licence is distributed on an "AS IS" basis,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
+implied. See the Licence for the specific language governing
+permissions and limitations under the Licence.
+-->
+<template>
+  <v-list-item
+    :disabled="readonly"
+    dense
+    nuxt
+    :to="path"
+  >
+    <v-list-item-content>
+      <v-list-item-title
+        :class="readonly? 'grey-text':'text'"
+      >
+        <v-icon
+          left
+          small
+          :color="readonly ? 'grey':'black'"
+        >
+          mdi-content-copy
+        </v-icon>
+        Copy
+      </v-list-item-title>
+    </v-list-item-content>
+  </v-list-item>
+</template>
+<script lang="ts">
+import { Component, Vue, Prop } from 'nuxt-property-decorator'
+@Component
+export default class DotMenuActionCopy extends Vue {
+  @Prop({
+    required: true,
+    type: String
+  })
+  readonly path!:string
+
+  @Prop({
+    default: false,
+    type: Boolean
+  })
+  // @ts-ignore
+  readonly readonly: boolean
+}
+</script>
diff --git a/components/DotMenuActionDelete.vue b/components/DotMenuActionDelete.vue
new file mode 100644
index 0000000000000000000000000000000000000000..448e40e52bc8d03155bf9c2097ccf4881825bc1c
--- /dev/null
+++ b/components/DotMenuActionDelete.vue
@@ -0,0 +1,69 @@
+<!--
+Web client of the Sensor Management System software developed within the
+Helmholtz DataHub Initiative by GFZ and UFZ.
+
+Copyright (C) 2020, 2021
+- Nils Brinckmann (GFZ, nils.brinckmann@gfz-potsdam.de)
+- Marc Hanisch (GFZ, marc.hanisch@gfz-potsdam.de)
+- Tobias Kuhnert (UFZ, tobias.kuhnert@ufz.de)
+- Erik Pongratz (UFZ, erik.pongratz@ufz.de)
+- Helmholtz Centre Potsdam - GFZ German Research Centre for
+  Geosciences (GFZ, https://www.gfz-potsdam.de)
+- Helmholtz Centre for Environmental Research GmbH - UFZ
+  (UFZ, https://www.ufz.de)
+
+Parts of this program were developed within the context of the
+following publicly funded projects or measures:
+- Helmholtz Earth and Environment DataHub
+  (https://www.helmholtz.de/en/research/earth_and_environment/initiatives/#h51095)
+
+Licensed under the HEESIL, Version 1.0 or - as soon they will be
+approved by the "Community" - subsequent versions of the HEESIL
+(the "Licence").
+
+You may not use this work except in compliance with the Licence.
+
+You may obtain a copy of the Licence at:
+https://gitext.gfz-potsdam.de/software/heesil
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the Licence is distributed on an "AS IS" basis,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
+implied. See the Licence for the specific language governing
+permissions and limitations under the Licence.
+-->
+<template>
+  <v-list-item
+    :disabled="readonly"
+    dense
+    @click="$emit('click')"
+  >
+    <v-list-item-content>
+      <v-list-item-title
+        :class="readonly ? 'grey--text' : 'red--text'"
+      >
+        <v-icon
+          left
+          small
+          :color="readonly ? 'grey' : 'red'"
+        >
+          mdi-delete
+        </v-icon>
+        Delete
+      </v-list-item-title>
+    </v-list-item-content>
+  </v-list-item>
+</template>
+<script lang="ts">
+import { Component, Vue, Prop } from 'nuxt-property-decorator'
+
+@Component
+export default class DotMenuActionDelete extends Vue {
+  @Prop({
+    default: false,
+    type: Boolean
+  })
+  // @ts-ignore
+  readonly readonly: boolean
+}
+</script>
diff --git a/components/configurations/ConfigurationsDeleteDialog.vue b/components/configurations/ConfigurationsDeleteDialog.vue
index ba7aa6d5e3d315b9960c6c149d258d0a281a05fd..a484b8326708c7c298dbf13e1cbabf1d98d17e14 100644
--- a/components/configurations/ConfigurationsDeleteDialog.vue
+++ b/components/configurations/ConfigurationsDeleteDialog.vue
@@ -43,7 +43,7 @@ permissions and limitations under the Licence.
         Delete configuration
       </v-card-title>
       <v-card-text>
-        Do you really want to delete the configuration <em>{{ configurationToDelete.label }}</em>
+        Do you really want to delete the configuration <em>{{ configurationToDelete.label }}</em>?
       </v-card-text>
       <v-card-actions>
         <v-btn
diff --git a/components/configurations/ConfigurationsOverviewCard.vue b/components/configurations/ConfigurationsOverviewCard.vue
index cfeced0945ebd3d652a7aa3d670ac854ee805242..6028222296df221939ccab1800c75d5ad8020ddc 100644
--- a/components/configurations/ConfigurationsOverviewCard.vue
+++ b/components/configurations/ConfigurationsOverviewCard.vue
@@ -58,51 +58,14 @@ permissions and limitations under the Licence.
               align-self="end"
               class="text-right"
             >
-              <v-menu
-                close-on-click
-                close-on-content-click
-                offset-x
-                left
-                z-index="999"
-              >
-                <template #activator="{ on }">
-                  <v-btn
-                    data-role="property-menu"
-                    icon
-                    small
-                    v-on="on"
-                  >
-                    <v-icon
-                      dense
-                      small
-                    >
-                      mdi-dots-vertical
-                    </v-icon>
-                  </v-btn>
-                </template>
-                <v-list>
-                  <v-list-item
-                    :disabled="!isUserAuthenticated"
-                    dense
+              <DotMenu>
+                <template #actions>
+                  <DotMenuActionDelete
+                    :readonly="!isUserAuthenticated"
                     @click="$emit('showDeleteDialog',configuration)"
-                  >
-                    <v-list-item-content>
-                      <v-list-item-title
-                        :class="isUserAuthenticated ? 'red--text' : 'grey--text'"
-                      >
-                        <v-icon
-                          left
-                          small
-                          :color="isUserAuthenticated ? 'red' : 'grey'"
-                        >
-                          mdi-delete
-                        </v-icon>
-                        Delete
-                      </v-list-item-title>
-                    </v-list-item-content>
-                  </v-list-item>
-                </v-list>
-              </v-menu>
+                  />
+                </template>
+              </DotMenu>
             </v-col>
           </v-row>
           <v-row
@@ -218,10 +181,12 @@ import { DynamicLocation, LocationType, StationaryLocation } from '@/models/Loca
 import { dateToDateTimeString } from '@/utils/dateHelper'
 
 import StatusBadge from '@/components/StatusBadge.vue'
+import DotMenu from '@/components/DotMenu.vue'
+import DotMenuActionDelete from '@/components/DotMenuActionDelete.vue'
 
 @Component({
   filters: { dateToDateTimeString },
-  components: { StatusBadge }
+  components: { DotMenuActionDelete, DotMenu, StatusBadge }
 })
 export default class ConfigurationsOverviewCard extends Vue {
   @Prop({
diff --git a/components/contacts/ContacsDeleteDialog.vue b/components/contacts/ContacsDeleteDialog.vue
new file mode 100644
index 0000000000000000000000000000000000000000..5dde8439d86e192ca9da2bc5a3bb89b9e6a9a016
--- /dev/null
+++ b/components/contacts/ContacsDeleteDialog.vue
@@ -0,0 +1,104 @@
+<!--
+Web client of the Sensor Management System software developed within the
+Helmholtz DataHub Initiative by GFZ and UFZ.
+
+Copyright (C) 2020, 2021
+- Nils Brinckmann (GFZ, nils.brinckmann@gfz-potsdam.de)
+- Marc Hanisch (GFZ, marc.hanisch@gfz-potsdam.de)
+- Tobias Kuhnert (UFZ, tobias.kuhnert@ufz.de)
+- Erik Pongratz (UFZ, erik.pongratz@ufz.de)
+- Helmholtz Centre Potsdam - GFZ German Research Centre for
+  Geosciences (GFZ, https://www.gfz-potsdam.de)
+- Helmholtz Centre for Environmental Research GmbH - UFZ
+  (UFZ, https://www.ufz.de)
+
+Parts of this program were developed within the context of the
+following publicly funded projects or measures:
+- Helmholtz Earth and Environment DataHub
+  (https://www.helmholtz.de/en/research/earth_and_environment/initiatives/#h51095)
+
+Licensed under the HEESIL, Version 1.0 or - as soon they will be
+approved by the "Community" - subsequent versions of the HEESIL
+(the "Licence").
+
+You may not use this work except in compliance with the Licence.
+
+You may obtain a copy of the Licence at:
+https://gitext.gfz-potsdam.de/software/heesil
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the Licence is distributed on an "AS IS" basis,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
+implied. See the Licence for the specific language governing
+permissions and limitations under the Licence.
+-->
+<template>
+  <v-dialog
+    v-model="showDialog"
+    max-width="290"
+    @click:outside="$emit('cancel-deletion')"
+  >
+    <v-card v-if="hasContactToDelete">
+      <v-card-title class="headline">
+        Delete contact
+      </v-card-title>
+      <v-card-text>
+        Do you really want to delete the contact <em>{{ contactToDelete.fullName }}</em>?
+      </v-card-text>
+      <v-card-actions>
+        <v-btn
+          text
+          @click="$emit('cancel-deletion')"
+        >
+          No
+        </v-btn>
+        <v-spacer />
+        <v-btn
+          color="error"
+          text
+          @click="$emit('submit-deletion')"
+        >
+          <v-icon left>
+            mdi-delete
+          </v-icon>
+          Delete
+        </v-btn>
+      </v-card-actions>
+    </v-card>
+  </v-dialog>
+</template>
+
+<script lang="ts">
+import { Component, Prop, Vue } from 'nuxt-property-decorator'
+import { Contact } from '@/models/Contact'
+
+@Component
+export default class ContacsDeleteDialog extends Vue {
+  @Prop({
+    required: true,
+    type: Boolean
+  })
+  readonly value!: boolean
+
+  @Prop({
+    type: Object
+  })
+  contactToDelete!: Contact
+
+  get showDialog (): boolean {
+    return this.value
+  }
+
+  set showDialog (value: boolean) {
+    this.$emit('input', value)
+  }
+
+  get hasContactToDelete () {
+    return this.contactToDelete !== null
+  }
+}
+</script>
+
+<style scoped>
+
+</style>
diff --git a/components/device/DeviceDeleteDialog.vue b/components/device/DeviceDeleteDialog.vue
new file mode 100644
index 0000000000000000000000000000000000000000..eae913bdc79c25752dcdab36d75dca3df50d3e71
--- /dev/null
+++ b/components/device/DeviceDeleteDialog.vue
@@ -0,0 +1,104 @@
+<!--
+Web client of the Sensor Management System software developed within the
+Helmholtz DataHub Initiative by GFZ and UFZ.
+
+Copyright (C) 2020, 2021
+- Nils Brinckmann (GFZ, nils.brinckmann@gfz-potsdam.de)
+- Marc Hanisch (GFZ, marc.hanisch@gfz-potsdam.de)
+- Tobias Kuhnert (UFZ, tobias.kuhnert@ufz.de)
+- Erik Pongratz (UFZ, erik.pongratz@ufz.de)
+- Helmholtz Centre Potsdam - GFZ German Research Centre for
+  Geosciences (GFZ, https://www.gfz-potsdam.de)
+- Helmholtz Centre for Environmental Research GmbH - UFZ
+  (UFZ, https://www.ufz.de)
+
+Parts of this program were developed within the context of the
+following publicly funded projects or measures:
+- Helmholtz Earth and Environment DataHub
+  (https://www.helmholtz.de/en/research/earth_and_environment/initiatives/#h51095)
+
+Licensed under the HEESIL, Version 1.0 or - as soon they will be
+approved by the "Community" - subsequent versions of the HEESIL
+(the "Licence").
+
+You may not use this work except in compliance with the Licence.
+
+You may obtain a copy of the Licence at:
+https://gitext.gfz-potsdam.de/software/heesil
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the Licence is distributed on an "AS IS" basis,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
+implied. See the Licence for the specific language governing
+permissions and limitations under the Licence.
+-->
+<template>
+  <v-dialog
+    v-model="showDialog"
+    max-width="290"
+    @click:outside="$emit('cancel-deletion')"
+  >
+    <v-card v-if="hasDeviceToDelete">
+      <v-card-title class="headline">
+        Delete device
+      </v-card-title>
+      <v-card-text>
+        Do you really want to delete the device <em>{{ deviceToDelete.shortName }}</em>?
+      </v-card-text>
+      <v-card-actions>
+        <v-btn
+          text
+          @click="$emit('cancel-deletion')"
+        >
+          No
+        </v-btn>
+        <v-spacer />
+        <v-btn
+          color="error"
+          text
+          @click="$emit('submit-deletion')"
+        >
+          <v-icon left>
+            mdi-delete
+          </v-icon>
+          Delete
+        </v-btn>
+      </v-card-actions>
+    </v-card>
+  </v-dialog>
+</template>
+
+<script lang="ts">
+import { Component, Prop, Vue } from 'nuxt-property-decorator'
+import { Device } from '@/models/Device'
+
+@Component
+export default class DeviceDeleteDialog extends Vue {
+  @Prop({
+    required: true,
+    type: Boolean
+  })
+  readonly value!: boolean
+
+  @Prop({
+    type: Object
+  })
+  deviceToDelete!: Device
+
+  get showDialog (): boolean {
+    return this.value
+  }
+
+  set showDialog (value: boolean) {
+    this.$emit('input', value)
+  }
+
+  get hasDeviceToDelete () {
+    return this.deviceToDelete !== null
+  }
+}
+</script>
+
+<style scoped>
+
+</style>
diff --git a/components/plarform/PlatformDeleteDialog.vue b/components/plarform/PlatformDeleteDialog.vue
new file mode 100644
index 0000000000000000000000000000000000000000..2aa2a13111f851bf40cbfcf10b45a79230c9884d
--- /dev/null
+++ b/components/plarform/PlatformDeleteDialog.vue
@@ -0,0 +1,104 @@
+<!--
+Web client of the Sensor Management System software developed within the
+Helmholtz DataHub Initiative by GFZ and UFZ.
+
+Copyright (C) 2020, 2021
+- Nils Brinckmann (GFZ, nils.brinckmann@gfz-potsdam.de)
+- Marc Hanisch (GFZ, marc.hanisch@gfz-potsdam.de)
+- Tobias Kuhnert (UFZ, tobias.kuhnert@ufz.de)
+- Erik Pongratz (UFZ, erik.pongratz@ufz.de)
+- Helmholtz Centre Potsdam - GFZ German Research Centre for
+  Geosciences (GFZ, https://www.gfz-potsdam.de)
+- Helmholtz Centre for Environmental Research GmbH - UFZ
+  (UFZ, https://www.ufz.de)
+
+Parts of this program were developed within the context of the
+following publicly funded projects or measures:
+- Helmholtz Earth and Environment DataHub
+  (https://www.helmholtz.de/en/research/earth_and_environment/initiatives/#h51095)
+
+Licensed under the HEESIL, Version 1.0 or - as soon they will be
+approved by the "Community" - subsequent versions of the HEESIL
+(the "Licence").
+
+You may not use this work except in compliance with the Licence.
+
+You may obtain a copy of the Licence at:
+https://gitext.gfz-potsdam.de/software/heesil
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the Licence is distributed on an "AS IS" basis,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
+implied. See the Licence for the specific language governing
+permissions and limitations under the Licence.
+-->
+<template>
+  <v-dialog
+    v-model="showDialog"
+    max-width="290"
+    @click:outside="$emit('cancel-deletion')"
+  >
+    <v-card v-if="hasPlatformToDelete">
+      <v-card-title class="headline">
+        Delete platform
+      </v-card-title>
+      <v-card-text>
+        Do you really want to delete the platform <em>{{ platformToDelete.shortName }}</em>?
+      </v-card-text>
+      <v-card-actions>
+        <v-btn
+          text
+          @click="$emit('cancel-deletion')"
+        >
+          No
+        </v-btn>
+        <v-spacer />
+        <v-btn
+          color="error"
+          text
+          @click="$emit('submit-deletion')"
+        >
+          <v-icon left>
+            mdi-delete
+          </v-icon>
+          Delete
+        </v-btn>
+      </v-card-actions>
+    </v-card>
+  </v-dialog>
+</template>
+
+<script lang="ts">
+import { Component, Prop, Vue } from 'nuxt-property-decorator'
+import { Platform } from '@/models/Platform'
+
+@Component
+export default class PlatformDeleteDialog extends Vue {
+  @Prop({
+    required: true,
+    type: Boolean
+  })
+  readonly value!: boolean
+
+  @Prop({
+    type: Object
+  })
+  platformToDelete!: Platform
+
+  get showDialog (): boolean {
+    return this.value
+  }
+
+  set showDialog (value: boolean) {
+    this.$emit('input', value)
+  }
+
+  get hasPlatformToDelete () {
+    return this.platformToDelete !== null
+  }
+}
+</script>
+
+<style scoped>
+
+</style>
diff --git a/models/Contact.ts b/models/Contact.ts
index 97713340034a1c3a336dde14a3694c89172941cc..aacd2a0731b7d14fa0f9626526445a80d991e653 100644
--- a/models/Contact.ts
+++ b/models/Contact.ts
@@ -76,6 +76,10 @@ export class Contact implements IContact {
     this._familyName = newFamilyName
   }
 
+  get fullName ():string {
+    return this.givenName + ' ' + this.familyName
+  }
+
   get website (): string {
     return this._website
   }
diff --git a/pages/configurations/_id/basic/index.vue b/pages/configurations/_id/basic/index.vue
index 35d6fa293da5bd2096b07399452c305fea09e5f6..0a2b439716cb2c4ce52b808af734ee97f073f46f 100644
--- a/pages/configurations/_id/basic/index.vue
+++ b/pages/configurations/_id/basic/index.vue
@@ -45,6 +45,15 @@ permissions and limitations under the Licence.
       >
         Edit
       </v-btn>
+      <DotMenu
+        v-if="$auth.loggedIn"
+      >
+        <template #actions>
+          <DotMenuActionDelete
+            @click="initDeleteDialog"
+          />
+        </template>
+      </DotMenu>
     </v-card-actions>
 
     <ConfigurationsBasicData
@@ -63,7 +72,22 @@ permissions and limitations under the Licence.
       >
         Edit
       </v-btn>
+      <DotMenu
+        v-if="$auth.loggedIn"
+      >
+        <template #actions>
+          <DotMenuActionDelete
+            @click="initDeleteDialog"
+          />
+        </template>
+      </DotMenu>
     </v-card-actions>
+    <ConfigurationsDeleteDialog
+      v-model="showDeleteDialog"
+      :configuration-to-delete="configuration"
+      @cancel-deletion="closeDialog"
+      @submit-deletion="deleteAndCloseDialog"
+    />
   </v-card>
 </template>
 
@@ -72,8 +96,11 @@ import { Component, Prop, Vue } from 'nuxt-property-decorator'
 import { Configuration } from '@/models/Configuration'
 import ConfigurationsBasicDataForm from '@/components/configurations/ConfigurationsBasicDataForm.vue'
 import ConfigurationsBasicData from '@/components/configurations/ConfigurationsBasicData.vue'
+import DotMenu from '@/components/DotMenu.vue'
+import DotMenuActionDelete from '@/components/DotMenuActionDelete.vue'
+import ConfigurationsDeleteDialog from '@/components/configurations/ConfigurationsDeleteDialog.vue'
 @Component({
-  components: { ConfigurationsBasicData, ConfigurationsBasicDataForm }
+  components: { ConfigurationsDeleteDialog, DotMenuActionDelete, DotMenu, ConfigurationsBasicData, ConfigurationsBasicDataForm }
 })
 export default class ConfigurationShowBasicPage extends Vue {
   @Prop({
@@ -82,6 +109,8 @@ export default class ConfigurationShowBasicPage extends Vue {
   })
   readonly value!: Configuration
 
+  private showDeleteDialog:boolean=false;
+
   head () {
     return {
       titleTemplate: 'Basic Data - %s'
@@ -99,6 +128,28 @@ export default class ConfigurationShowBasicPage extends Vue {
   get configurationId () {
     return this.$route.params.id
   }
+
+  initDeleteDialog () {
+    this.showDeleteDialog = true
+  }
+
+  closeDialog () {
+    this.showDeleteDialog = false
+  }
+
+  deleteAndCloseDialog () {
+    this.showDeleteDialog = false
+    if (this.configuration === null) {
+      return
+    }
+
+    this.$api.configurations.deleteById(this.configuration.id).then(() => {
+      this.$router.push('/configurations')
+      this.$store.commit('snackbar/setSuccess', 'Configuration deleted')
+    }).catch((_error) => {
+      this.$store.commit('snackbar/setError', 'Configuration could not be deleted')
+    })
+  }
 }
 </script>
 
diff --git a/pages/contacts/_contactId.vue b/pages/contacts/_contactId.vue
index 0a31d1492f4d0cdb68002c25037ea1d2b62dcf9b..57a14fd7fbd4fd39635e19ecea05467ca758d53e 100644
--- a/pages/contacts/_contactId.vue
+++ b/pages/contacts/_contactId.vue
@@ -51,6 +51,15 @@ permissions and limitations under the Licence.
           >
             Edit
           </v-btn>
+          <DotMenu
+            v-if="$auth.loggedIn"
+          >
+            <template #actions>
+              <DotMenuActionDelete
+                @click="initDeleteDialog"
+              />
+            </template>
+          </DotMenu>
         </v-card-actions>
         <ContactBasicData
           v-model="contact"
@@ -66,8 +75,23 @@ permissions and limitations under the Licence.
           >
             Edit
           </v-btn>
+          <DotMenu
+            v-if="$auth.loggedIn"
+          >
+            <template #actions>
+              <DotMenuActionDelete
+                @click="initDeleteDialog"
+              />
+            </template>
+          </DotMenu>
         </v-card-actions>
       </div>
+      <ContacsDeleteDialog
+        v-model="showDeleteDialog"
+        :contact-to-delete="contact"
+        @cancel-deletion="closeDialog"
+        @submit-deletion="deleteAndCloseDialog"
+      />
     </v-card>
   </div>
 </template>
@@ -78,9 +102,15 @@ import { Contact } from '@/models/Contact'
 
 import ContactBasicData from '@/components/ContactBasicData.vue'
 import ProgressIndicator from '@/components/ProgressIndicator.vue'
+import DotMenu from '@/components/DotMenu.vue'
+import DotMenuActionDelete from '@/components/DotMenuActionDelete.vue'
+import ContacsDeleteDialog from '@/components/contacts/ContacsDeleteDialog.vue'
 
 @Component({
   components: {
+    ContacsDeleteDialog,
+    DotMenuActionDelete,
+    DotMenu,
     ContactBasicData,
     ProgressIndicator
   }
@@ -89,6 +119,8 @@ export default class ContactShowPage extends Vue {
   private contact: Contact = new Contact()
   private isLoading: boolean = true
 
+  private showDeleteDialog:boolean=false;
+
   created () {
     this.initializeAppBar()
   }
@@ -117,6 +149,28 @@ export default class ContactShowPage extends Vue {
     return this.$route.params.contactId
   }
 
+  initDeleteDialog () {
+    this.showDeleteDialog = true
+  }
+
+  closeDialog () {
+    this.showDeleteDialog = false
+  }
+
+  deleteAndCloseDialog () {
+    this.showDeleteDialog = false
+    if (this.contact === null) {
+      return
+    }
+
+    this.$api.contacts.deleteById(this.contact.id!).then(() => {
+      this.$router.push('/contacts')
+      this.$store.commit('snackbar/setSuccess', 'Contact deleted')
+    }).catch((_error) => {
+      this.$store.commit('snackbar/setError', 'Contact could not be deleted')
+    })
+  }
+
   @Watch('contact', { immediate: true, deep: true })
   // @ts-ignore
   onContactChanged (val: Contact) {
diff --git a/pages/contacts/index.vue b/pages/contacts/index.vue
index 9076764adb838b5656f4768a6e118f1bee580249..db9dd98289c5ec90f48cb44e4bc2253cac1811de 100644
--- a/pages/contacts/index.vue
+++ b/pages/contacts/index.vue
@@ -104,58 +104,21 @@ permissions and limitations under the Licence.
                 align-self="end"
                 class="text-right"
               >
-                <v-menu
-                  close-on-click
-                  close-on-content-click
-                  offset-x
-                  left
-                  z-index="999"
-                >
-                  <template #activator="{ on }">
-                    <v-btn
-                      data-role="property-menu"
-                      icon
-                      small
-                      v-on="on"
-                    >
-                      <v-icon
-                        dense
-                        small
-                      >
-                        mdi-dots-vertical
-                      </v-icon>
-                    </v-btn>
+                <DotMenu>
+                  <template #actions>
+                    <DotMenuActionDelete
+                      :readonly="!$auth.loggedIn"
+                      @click="initDeleteDialog(result)"
+                    />
                   </template>
-                  <v-list>
-                    <v-list-item
-                      :disabled="!$auth.loggedIn"
-                      dense
-                      @click="showDeleteDialogFor(result.id)"
-                    >
-                      <v-list-item-content>
-                        <v-list-item-title
-                          :class="$auth.loggedIn ? 'red--text' : 'grey--text'"
-                        >
-                          <v-icon
-                            left
-                            small
-                            :color="$auth.loggedIn ? 'red' : 'grey'"
-                          >
-                            mdi-delete
-                          </v-icon>
-                          Delete
-                        </v-list-item-title>
-                      </v-list-item-content>
-                    </v-list-item>
-                  </v-list>
-                </v-menu>
+                </DotMenu>
               </v-col>
             </v-row>
             <v-row
               no-gutters
             >
               <v-col class="text-subtitle-1">
-                {{ getFullName(result) }}
+                {{ result.fullName }}
               </v-col>
               <v-col
                 align-self="end"
@@ -263,38 +226,15 @@ permissions and limitations under the Licence.
               </v-card-text>
             </v-card>
           </v-expand-transition>
-          <v-dialog v-model="showDeleteDialog[result.id]" max-width="290">
-            <v-card>
-              <v-card-title class="headline">
-                Delete contact
-              </v-card-title>
-              <v-card-text>
-                Do you really want to delete the contact <em>{{ getFullName(result) }}</em>?
-              </v-card-text>
-              <v-card-actions>
-                <v-btn
-                  text
-                  @click="hideDeleteDialogFor(result.id)"
-                >
-                  No
-                </v-btn>
-                <v-spacer />
-                <v-btn
-                  color="error"
-                  text
-                  @click="deleteAndCloseDialog(result.id)"
-                >
-                  <v-icon left>
-                    mdi-delete
-                  </v-icon>
-                  Delete
-                </v-btn>
-              </v-card-actions>
-            </v-card>
-          </v-dialog>
         </v-card>
       </v-hover>
     </div>
+    <ContacsDeleteDialog
+      v-model="showDeleteDialog"
+      :contact-to-delete="contactToDelete"
+      @cancel-deletion="closeDialog"
+      @submit-deletion="deleteAndCloseDialog"
+    />
     <v-btn
       v-if="$auth.loggedIn"
       bottom
@@ -318,8 +258,12 @@ import { Component, Vue } from 'nuxt-property-decorator'
 import { IPaginationLoader } from '@/utils/PaginatedLoader'
 
 import { Contact } from '@/models/Contact'
+import DotMenu from '@/components/DotMenu.vue'
+import DotMenuActionDelete from '@/components/DotMenuActionDelete.vue'
+import ContacsDeleteDialog from '@/components/contacts/ContacsDeleteDialog.vue'
 
 @Component({
+  components: { ContacsDeleteDialog, DotMenuActionDelete, DotMenu }
 })
 export default class SearchContactsPage extends Vue {
   private readonly pageSize: number = 20
@@ -331,7 +275,8 @@ export default class SearchContactsPage extends Vue {
   private searchResults: Contact[] = []
   private searchText: string = ''
 
-  private showDeleteDialog: { [id: string]: boolean} = {}
+  private showDeleteDialog:boolean=false
+  private contactToDelete:Contact|null=null
   private searchResultItemsShown: { [id: string]: boolean } = {}
 
   created () {
@@ -352,7 +297,6 @@ export default class SearchContactsPage extends Vue {
 
   beforeDestroy () {
     this.unsetResultItemsShown()
-    this.showDeleteDialog = {}
     this.$store.dispatch('appbar/setDefaults')
   }
 
@@ -373,7 +317,6 @@ export default class SearchContactsPage extends Vue {
     this.loading = true
     this.searchResults = []
     this.unsetResultItemsShown()
-    this.showDeleteDialog = {}
     const lastActiveSearcher = this.$api.contacts
       .newSearchBuilder()
       .withText(this.searchText)
@@ -418,31 +361,36 @@ export default class SearchContactsPage extends Vue {
     return this.loader != null && this.loader.funToLoadNext != null
   }
 
-  deleteAndCloseDialog (id: string) {
-    this.$api.contacts.deleteById(id).then(() => {
-      this.showDeleteDialog = {}
+  initDeleteDialog (contact: Contact) {
+    this.showDeleteDialog = true
+    this.contactToDelete = contact
+  }
+
+  closeDialog () {
+    this.showDeleteDialog = false
+    this.contactToDelete = null
+  }
 
-      const searchIndex = this.searchResults.findIndex(r => r.id === id)
+  deleteAndCloseDialog () {
+    this.showDeleteDialog = false
+    if (this.contactToDelete === null) {
+      return
+    }
+
+    this.$api.contacts.deleteById(this.contactToDelete.id!).then(() => {
+      const searchIndex = this.searchResults.findIndex(contact => contact.id === this.contactToDelete?.id)
       if (searchIndex > -1) {
         this.searchResults.splice(searchIndex, 1)
         this.totalCount -= 1
       }
-
       this.$store.commit('snackbar/setSuccess', 'Contact deleted')
     }).catch((_error) => {
-      this.showDeleteDialog = {}
       this.$store.commit('snackbar/setError', 'Contact could not be deleted')
+    }).finally(() => {
+      this.contactToDelete = null
     })
   }
 
-  showDeleteDialogFor (id: string) {
-    Vue.set(this.showDeleteDialog, id, true)
-  }
-
-  hideDeleteDialogFor (id: string) {
-    Vue.set(this.showDeleteDialog, id, false)
-  }
-
   showResultItem (id: string) {
     const show = !!this.searchResultItemsShown[id]
     Vue.set(this.searchResultItemsShown, id, !show)
@@ -455,10 +403,6 @@ export default class SearchContactsPage extends Vue {
   unsetResultItemsShown (): void {
     this.searchResultItemsShown = {}
   }
-
-  getFullName (contact: Contact) : string {
-    return contact.givenName + ' ' + contact.familyName
-  }
 }
 </script>
 
diff --git a/pages/devices/_deviceId/basic/index.vue b/pages/devices/_deviceId/basic/index.vue
index 94a47eb1160f96765b44d96db490d43ba0240b3d..ca548544db907fee6d16fc221404341c64dacbbf 100644
--- a/pages/devices/_deviceId/basic/index.vue
+++ b/pages/devices/_deviceId/basic/index.vue
@@ -41,6 +41,18 @@ permissions and limitations under the Licence.
       >
         Edit
       </v-btn>
+      <DotMenu
+        v-if="$auth.loggedIn"
+      >
+        <template #actions>
+          <DotMenuActionCopy
+            :path="'/devices/copy/' + deviceId"
+          />
+          <DotMenuActionDelete
+            @click="initDeleteDialog"
+          />
+        </template>
+      </DotMenu>
     </v-card-actions>
     <DeviceBasicData
       v-model="device"
@@ -56,7 +68,25 @@ permissions and limitations under the Licence.
       >
         Edit
       </v-btn>
+      <DotMenu
+        v-if="$auth.loggedIn"
+      >
+        <template #actions>
+          <DotMenuActionCopy
+            :path="'/devices/copy/' + deviceId"
+          />
+          <DotMenuActionDelete
+            @click="initDeleteDialog"
+          />
+        </template>
+      </DotMenu>
     </v-card-actions>
+    <DeviceDeleteDialog
+      v-model="showDeleteDialog"
+      :device-to-delete="device"
+      @cancel-deletion="closeDialog"
+      @submit-deletion="deleteAndCloseDialog"
+    />
   </div>
 </template>
 
@@ -66,9 +96,17 @@ import { Component, Vue, Prop } from 'nuxt-property-decorator'
 import DeviceBasicData from '@/components/DeviceBasicData.vue'
 
 import { Device } from '@/models/Device'
+import DeviceDeleteDialog from '@/components/device/DeviceDeleteDialog.vue'
+import DotMenu from '@/components/DotMenu.vue'
+import DotMenuActionCopy from '@/components/DotMenuActionCopy.vue'
+import DotMenuActionDelete from '@/components/DotMenuActionDelete.vue'
 
 @Component({
   components: {
+    DotMenuActionDelete,
+    DotMenuActionCopy,
+    DotMenu,
+    DeviceDeleteDialog,
     DeviceBasicData
   }
 })
@@ -79,6 +117,8 @@ export default class DeviceShowBasicPage extends Vue {
   })
   readonly value!: Device
 
+  private showDeleteDialog:boolean=false;
+
   get device (): Device {
     return this.value
   }
@@ -90,5 +130,27 @@ export default class DeviceShowBasicPage extends Vue {
   get deviceId () {
     return this.$route.params.deviceId
   }
+
+  initDeleteDialog () {
+    this.showDeleteDialog = true
+  }
+
+  closeDialog () {
+    this.showDeleteDialog = false
+  }
+
+  deleteAndCloseDialog () {
+    this.showDeleteDialog = false
+    if (this.device === null) {
+      return
+    }
+
+    this.$api.devices.deleteById(this.device.id!).then(() => {
+      this.$router.push('/devices')
+      this.$store.commit('snackbar/setSuccess', 'Device deleted')
+    }).catch((_error) => {
+      this.$store.commit('snackbar/setError', 'Device could not be deleted')
+    })
+  }
 }
 </script>
diff --git a/pages/devices/index.vue b/pages/devices/index.vue
index 094890280ac1fac2634ab1cd146534cf756ec3e9..803591292732fc8331cf48e3cde69da2f4399222 100644
--- a/pages/devices/index.vue
+++ b/pages/devices/index.vue
@@ -212,72 +212,18 @@ permissions and limitations under the Licence.
                 align-self="end"
                 class="text-right"
               >
-                <v-menu
-                  close-on-click
-                  close-on-content-click
-                  offset-x
-                  left
-                  z-index="999"
-                >
-                  <template #activator="{ on }">
-                    <v-btn
-                      data-role="property-menu"
-                      icon
-                      small
-                      v-on="on"
-                    >
-                      <v-icon
-                        dense
-                        small
-                      >
-                        mdi-dots-vertical
-                      </v-icon>
-                    </v-btn>
+                <DotMenu>
+                  <template #actions>
+                    <DotMenuActionCopy
+                      :readonly="!$auth.loggedIn"
+                      :path="'/devices/copy/' + result.id"
+                    />
+                    <DotMenuActionDelete
+                      :readonly="!$auth.loggedIn"
+                      @click="initDeleteDialog(result)"
+                    />
                   </template>
-
-                  <v-list>
-                    <v-list-item
-                      :disabled="!$auth.loggedIn"
-                      dense
-                      :to="'/devices/copy/' + result.id"
-                    >
-                      <v-list-item-content>
-                        <v-list-item-title
-                          :class="$auth.loggedIn ? 'text' : 'grey-text'"
-                        >
-                          <v-icon
-                            left
-                            small
-                            :color="$auth.loggedIn ? 'black' : 'grey'"
-                          >
-                            mdi-content-copy
-                          </v-icon>
-                          Copy
-                        </v-list-item-title>
-                      </v-list-item-content>
-                    </v-list-item>
-                    <v-list-item
-                      :disabled="!$auth.loggedIn"
-                      dense
-                      @click="showDeleteDialogFor(result.id)"
-                    >
-                      <v-list-item-content>
-                        <v-list-item-title
-                          :class="$auth.loggedIn ? 'red--text' : 'grey--text'"
-                        >
-                          <v-icon
-                            left
-                            small
-                            :color="$auth.loggedIn ? 'red' : 'grey'"
-                          >
-                            mdi-delete
-                          </v-icon>
-                          Delete
-                        </v-list-item-title>
-                      </v-list-item-content>
-                    </v-list-item>
-                  </v-list>
-                </v-menu>
+                </DotMenu>
               </v-col>
             </v-row>
             <v-row
@@ -440,38 +386,15 @@ permissions and limitations under the Licence.
               </v-card-text>
             </v-card>
           </v-expand-transition>
-          <v-dialog v-model="showDeleteDialog[result.id]" max-width="290">
-            <v-card>
-              <v-card-title class="headline">
-                Delete device
-              </v-card-title>
-              <v-card-text>
-                Do you really want to delete the device <em>{{ result.shortName }}</em>?
-              </v-card-text>
-              <v-card-actions>
-                <v-btn
-                  text
-                  @click="hideDeleteDialogFor(result.id)"
-                >
-                  No
-                </v-btn>
-                <v-spacer />
-                <v-btn
-                  color="error"
-                  text
-                  @click="deleteAndCloseDialog(result.id)"
-                >
-                  <v-icon left>
-                    mdi-delete
-                  </v-icon>
-                  Delete
-                </v-btn>
-              </v-card-actions>
-            </v-card>
-          </v-dialog>
         </v-card>
       </v-hover>
     </div>
+    <DeviceDeleteDialog
+      v-model="showDeleteDialog"
+      :device-to-delete="deviceToDelete"
+      @cancel-deletion="closeDialog"
+      @submit-deletion="deleteAndCloseDialog"
+    />
     <v-btn
       v-if="$auth.loggedIn"
       bottom
@@ -508,6 +431,10 @@ import { DeviceType } from '@/models/DeviceType'
 import { Manufacturer } from '@/models/Manufacturer'
 import { Status } from '@/models/Status'
 import { DeviceSearcher } from '@/services/sms/DeviceApi'
+import DeviceDeleteDialog from '@/components/device/DeviceDeleteDialog.vue'
+import DotMenu from '@/components/DotMenu.vue'
+import DotMenuActionCopy from '@/components/DotMenuActionCopy.vue'
+import DotMenuActionDelete from '@/components/DotMenuActionDelete.vue'
 
 interface IRunSearchParameters {
   searchText: string | null
@@ -547,6 +474,10 @@ class BasicSearchParameters implements IRunSearchParameters {
 
 @Component({
   components: {
+    DotMenuActionDelete,
+    DotMenuActionCopy,
+    DotMenu,
+    DeviceDeleteDialog,
     DeviceTypeSelect,
     ManufacturerSelect,
     StatusBadge,
@@ -573,12 +504,14 @@ export default class SearchDevicesPage extends Vue {
   private searchResults: Device[] = []
   private searchText: string | null = null
 
-  private showDeleteDialog: { [id: string]: boolean} = {}
+  private showDeleteDialog:boolean=false;
 
   private searchResultItemsShown: { [id: string]: boolean } = {}
 
   public readonly NO_TYPE: string = 'Unknown type'
 
+  private deviceToDelete:Device|null=null
+
   created () {
     this.initializeAppBar()
   }
@@ -622,7 +555,6 @@ export default class SearchDevicesPage extends Vue {
 
   beforeDestroy () {
     this.unsetResultItemsShown()
-    this.showDeleteDialog = {}
     this.$store.dispatch('appbar/setDefaults')
   }
 
@@ -688,7 +620,6 @@ export default class SearchDevicesPage extends Vue {
     this.loading = true
     this.searchResults = []
     this.unsetResultItemsShown()
-    this.showDeleteDialog = {}
     this.loader = null
 
     const searchBuilder = this.$api.devices
@@ -759,23 +690,6 @@ export default class SearchDevicesPage extends Vue {
     }
   }
 
-  deleteAndCloseDialog (id: string) {
-    this.$api.devices.deleteById(id).then(() => {
-      this.showDeleteDialog = {}
-
-      const searchIndex = this.searchResults.findIndex(r => r.id === id)
-      if (searchIndex > -1) {
-        this.searchResults.splice(searchIndex, 1)
-        this.totalCount -= 1
-      }
-
-      this.$store.commit('snackbar/setSuccess', 'Device deleted')
-    }).catch((_error) => {
-      this.showDeleteDialog = {}
-      this.$store.commit('snackbar/setError', 'Device could not be deleted')
-    })
-  }
-
   getType (device: Device) {
     if (this.deviceTypeLookup.has(device.deviceTypeUri)) {
       const deviceType: DeviceType = this.deviceTypeLookup.get(device.deviceTypeUri) as DeviceType
@@ -798,12 +712,34 @@ export default class SearchDevicesPage extends Vue {
     return ''
   }
 
-  showDeleteDialogFor (id: string) {
-    Vue.set(this.showDeleteDialog, id, true)
+  initDeleteDialog (device:Device) {
+    this.showDeleteDialog = true
+    this.deviceToDelete = device
+  }
+
+  closeDialog () {
+    this.showDeleteDialog = false
+    this.deviceToDelete = null
   }
 
-  hideDeleteDialogFor (id: string) {
-    Vue.set(this.showDeleteDialog, id, false)
+  deleteAndCloseDialog () {
+    this.showDeleteDialog = false
+    if (this.deviceToDelete === null) {
+      return
+    }
+
+    this.$api.devices.deleteById(this.deviceToDelete.id!).then(() => {
+      const searchIndex = this.searchResults.findIndex(device => device.id === this.deviceToDelete?.id)
+      if (searchIndex > -1) {
+        this.searchResults.splice(searchIndex, 1)
+        this.totalCount -= 1
+      }
+      this.$store.commit('snackbar/setSuccess', 'Device deleted')
+    }).catch((_error) => {
+      this.$store.commit('snackbar/setError', 'Device could not be deleted')
+    }).finally(() => {
+      this.deviceToDelete = null
+    })
   }
 
   showResultItem (id: string) {
diff --git a/pages/platforms/_platformId/basic/index.vue b/pages/platforms/_platformId/basic/index.vue
index 280c5cb736bf6f5e1762f5c5a7da6e77187e9479..405121fa94d397af87cf115664252db0d8507414 100644
--- a/pages/platforms/_platformId/basic/index.vue
+++ b/pages/platforms/_platformId/basic/index.vue
@@ -44,6 +44,18 @@ permissions and limitations under the Licence.
       >
         Edit
       </v-btn>
+      <DotMenu
+        v-if="$auth.loggedIn"
+      >
+        <template #actions>
+          <DotMenuActionCopy
+            :path="'/platforms/copy/' + platformId"
+          />
+          <DotMenuActionDelete
+            @click="initDeleteDialog"
+          />
+        </template>
+      </DotMenu>
     </v-card-actions>
     <PlatformBasicData
       v-model="platform"
@@ -59,7 +71,25 @@ permissions and limitations under the Licence.
       >
         Edit
       </v-btn>
+      <DotMenu
+        v-if="$auth.loggedIn"
+      >
+        <template #actions>
+          <DotMenuActionCopy
+            :path="'/platforms/copy/' + platformId"
+          />
+          <DotMenuActionDelete
+            @click="initDeleteDialog"
+          />
+        </template>
+      </DotMenu>
     </v-card-actions>
+    <PlatformDeleteDialog
+      v-model="showDeleteDialog"
+      :platform-to-delete="platform"
+      @cancel-deletion="closeDialog"
+      @submit-deletion="deleteAndCloseDialog"
+    />
   </div>
 </template>
 
@@ -68,9 +98,17 @@ import { Component, Prop, Vue } from 'nuxt-property-decorator'
 
 import { Platform } from '@/models/Platform'
 import PlatformBasicData from '@/components/PlatformBasicData.vue'
+import DotMenu from '@/components/DotMenu.vue'
+import DotMenuActionCopy from '@/components/DotMenuActionCopy.vue'
+import DotMenuActionDelete from '@/components/DotMenuActionDelete.vue'
+import PlatformDeleteDialog from '@/components/plarform/PlatformDeleteDialog.vue'
 
 @Component({
   components: {
+    PlatformDeleteDialog,
+    DotMenuActionDelete,
+    DotMenuActionCopy,
+    DotMenu,
     PlatformBasicData
   }
 })
@@ -81,6 +119,8 @@ export default class PlatformShowBasicPage extends Vue {
   })
   readonly value!: Platform
 
+  private showDeleteDialog:boolean=false;
+
   get platform (): Platform {
     return this.value
   }
@@ -92,5 +132,27 @@ export default class PlatformShowBasicPage extends Vue {
   get platformId () {
     return this.$route.params.platformId
   }
+
+  initDeleteDialog () {
+    this.showDeleteDialog = true
+  }
+
+  closeDialog () {
+    this.showDeleteDialog = false
+  }
+
+  deleteAndCloseDialog () {
+    this.showDeleteDialog = false
+    if (this.platform === null) {
+      return
+    }
+
+    this.$api.platforms.deleteById(this.platform.id!).then(() => {
+      this.$router.push('/platforms')
+      this.$store.commit('snackbar/setSuccess', 'Platform deleted')
+    }).catch((_error) => {
+      this.$store.commit('snackbar/setError', 'Platform could not be deleted')
+    })
+  }
 }
 </script>
diff --git a/pages/platforms/index.vue b/pages/platforms/index.vue
index ced017b8a373ec8f00109ee30b2b4a7a4f94275b..c3173bfbad4d2abe8e3638ff8ad0eed7edb48868 100644
--- a/pages/platforms/index.vue
+++ b/pages/platforms/index.vue
@@ -212,72 +212,18 @@ permissions and limitations under the Licence.
                 align-self="end"
                 class="text-right"
               >
-                <v-menu
-                  close-on-click
-                  close-on-content-click
-                  offset-x
-                  left
-                  z-index="999"
-                >
-                  <template #activator="{ on }">
-                    <v-btn
-                      data-role="property-menu"
-                      icon
-                      small
-                      v-on="on"
-                    >
-                      <v-icon
-                        dense
-                        small
-                      >
-                        mdi-dots-vertical
-                      </v-icon>
-                    </v-btn>
+                <DotMenu>
+                  <template #actions>
+                    <DotMenuActionCopy
+                      :readonly="!$auth.loggedIn"
+                      :path="'/platforms/copy/' + result.id"
+                    />
+                    <DotMenuActionDelete
+                      :readonly="!$auth.loggedIn"
+                      @click="initDeleteDialog(result)"
+                    />
                   </template>
-
-                  <v-list>
-                    <v-list-item
-                      :disabled="!$auth.loggedIn"
-                      dense
-                      :to="'/platforms/copy/' + result.id"
-                    >
-                      <v-list-item-content>
-                        <v-list-item-title
-                          :class="$auth.loggedIn ? 'text' : 'grey-text'"
-                        >
-                          <v-icon
-                            left
-                            small
-                            :color="$auth.loggedIn ? 'black' : 'grey'"
-                          >
-                            mdi-content-copy
-                          </v-icon>
-                          Copy
-                        </v-list-item-title>
-                      </v-list-item-content>
-                    </v-list-item>
-                    <v-list-item
-                      :disabled="!$auth.loggedIn"
-                      dense
-                      @click="showDeleteDialogFor(result.id)"
-                    >
-                      <v-list-item-content>
-                        <v-list-item-title
-                          :class="$auth.loggedIn ? 'red--text' : 'grey--text'"
-                        >
-                          <v-icon
-                            left
-                            small
-                            :color="$auth.loggedIn ? 'red' : 'grey'"
-                          >
-                            mdi-delete
-                          </v-icon>
-                          Delete
-                        </v-list-item-title>
-                      </v-list-item-content>
-                    </v-list-item>
-                  </v-list>
-                </v-menu>
+                </DotMenu>
               </v-col>
             </v-row>
             <v-row
@@ -440,38 +386,15 @@ permissions and limitations under the Licence.
               </v-card-text>
             </v-card>
           </v-expand-transition>
-          <v-dialog v-model="showDeleteDialog[result.id]" max-width="290">
-            <v-card>
-              <v-card-title class="headline">
-                Delete platform
-              </v-card-title>
-              <v-card-text>
-                Do you really want to delete the platform <em>{{ result.shortName }}</em>?
-              </v-card-text>
-              <v-card-actions>
-                <v-btn
-                  text
-                  @click="hideDeleteDialogFor(result.id)"
-                >
-                  No
-                </v-btn>
-                <v-spacer />
-                <v-btn
-                  color="error"
-                  text
-                  @click="deleteAndCloseDialog(result.id)"
-                >
-                  <v-icon left>
-                    mdi-delete
-                  </v-icon>
-                  Delete
-                </v-btn>
-              </v-card-actions>
-            </v-card>
-          </v-dialog>
         </v-card>
       </v-hover>
     </div>
+    <PlatformDeleteDialog
+      v-model="showDeleteDialog"
+      :platform-to-delete="platformToDelete"
+      @cancel-deletion="closeDialog"
+      @submit-deletion="deleteAndCloseDialog"
+    />
     <v-btn
       v-if="$auth.loggedIn"
       bottom
@@ -507,6 +430,10 @@ import { Platform } from '@/models/Platform'
 import { PlatformType } from '@/models/PlatformType'
 import { Status } from '@/models/Status'
 import { PlatformSearcher } from '@/services/sms/PlatformApi'
+import PlatformDeleteDialog from '@/components/plarform/PlatformDeleteDialog.vue'
+import DotMenu from '@/components/DotMenu.vue'
+import DotMenuActionCopy from '@/components/DotMenuActionCopy.vue'
+import DotMenuActionDelete from '@/components/DotMenuActionDelete.vue'
 
 interface IRunSearchParameters {
   searchText: string | null
@@ -546,6 +473,10 @@ class BasicSearchParameters implements IRunSearchParameters {
 
 @Component({
   components: {
+    DotMenuActionDelete,
+    DotMenuActionCopy,
+    DotMenu,
+    PlatformDeleteDialog,
     ManufacturerSelect,
     PlatformTypeSelect,
     StatusBadge,
@@ -572,12 +503,14 @@ export default class SearchPlatformsPage extends Vue {
   private searchResults: Platform[] = []
   private searchText: string | null = null
 
-  private showDeleteDialog: {[index: string]: boolean } = {}
+  private showDeleteDialog: boolean = false;
 
   private searchResultItemsShown: { [id: string]: boolean } = {}
 
   public readonly NO_TYPE: string = 'Unknown type'
 
+  private platformToDelete: Platform | null = null
+
   created () {
     this.initializeAppBar()
   }
@@ -621,7 +554,6 @@ export default class SearchPlatformsPage extends Vue {
 
   beforeDestroy () {
     this.unsetResultItemsShown()
-    this.showDeleteDialog = {}
     this.$store.dispatch('appbar/setDefaults')
   }
 
@@ -686,7 +618,6 @@ export default class SearchPlatformsPage extends Vue {
     this.loading = true
     this.searchResults = []
     this.unsetResultItemsShown()
-    this.showDeleteDialog = {}
     this.loader = null
 
     const searchBuilder = this.$api.platforms
@@ -757,11 +688,24 @@ export default class SearchPlatformsPage extends Vue {
     }
   }
 
-  deleteAndCloseDialog (id: string) {
-    this.$api.platforms.deleteById(id).then(() => {
-      this.showDeleteDialog = {}
+  initDeleteDialog (platform: Platform) {
+    this.showDeleteDialog = true
+    this.platformToDelete = platform
+  }
+
+  closeDialog () {
+    this.showDeleteDialog = false
+    this.platformToDelete = null
+  }
+
+  deleteAndCloseDialog () {
+    this.showDeleteDialog = false
+    if (this.platformToDelete === null) {
+      return
+    }
 
-      const searchIndex = this.searchResults.findIndex(r => r.id === id)
+    this.$api.platforms.deleteById(this.platformToDelete.id!).then(() => {
+      const searchIndex = this.searchResults.findIndex(r => r.id === this.platformToDelete?.id)
       if (searchIndex > -1) {
         this.searchResults.splice(searchIndex, 1)
         this.totalCount -= 1
@@ -769,8 +713,9 @@ export default class SearchPlatformsPage extends Vue {
 
       this.$store.commit('snackbar/setSuccess', 'Platform deleted')
     }).catch((_error) => {
-      this.showDeleteDialog = {}
       this.$store.commit('snackbar/setError', 'Platform could not be deleted')
+    }).finally(() => {
+      this.platformToDelete = null
     })
   }
 
@@ -796,14 +741,6 @@ export default class SearchPlatformsPage extends Vue {
     return ''
   }
 
-  showDeleteDialogFor (id: string) {
-    Vue.set(this.showDeleteDialog, id, true)
-  }
-
-  hideDeleteDialogFor (id: string) {
-    Vue.set(this.showDeleteDialog, id, false)
-  }
-
   showResultItem (id: string) {
     const show = !!this.searchResultItemsShown[id]
     Vue.set(this.searchResultItemsShown, id, !show)