feat(frontend): deleting stale components
This commit is contained in:
+2
-2
@@ -10,8 +10,8 @@
|
||||
</template>
|
||||
<script>
|
||||
import { signOut } from '@/auth-helpers'
|
||||
import userQuery from '../graphql/userById.gql'
|
||||
import UserAvatarIcon from '@/components/UserAvatarIcon'
|
||||
import userQuery from '@/graphql/userById.gql'
|
||||
import UserAvatarIcon from '@/cleanup/components/common/UserAvatarIcon'
|
||||
|
||||
export default {
|
||||
components: { UserAvatarIcon },
|
||||
@@ -123,7 +123,7 @@ export default {
|
||||
},
|
||||
cleanKey(key) {
|
||||
if (key === 'totalChildrenCount') return 'children count'
|
||||
if (key === 'speckle_type') return 'type'
|
||||
if (key === 'speckle_type') return 'speckle type'
|
||||
return key
|
||||
}
|
||||
}
|
||||
|
||||
+18
-2
@@ -1,5 +1,8 @@
|
||||
<template>
|
||||
<v-app id="speckle-auth">
|
||||
<v-app
|
||||
id="speckle-auth"
|
||||
:class="`${$vuetify.theme.dark ? 'background-dark' : 'background-light'}`"
|
||||
>
|
||||
<v-container fill-height fluid>
|
||||
<v-row align="center" justify="center">
|
||||
<v-col v-if="showBlurb" cols="12" md="6" lg="6" xl="4" class="hidden-sm-and-down">
|
||||
@@ -19,7 +22,7 @@
|
||||
</template>
|
||||
<script>
|
||||
import gql from 'graphql-tag'
|
||||
import Blurb from '../components/auth/Blurb'
|
||||
import Blurb from '@/cleanup/components/auth/Blurb'
|
||||
|
||||
export default {
|
||||
components: { Blurb },
|
||||
@@ -50,3 +53,16 @@ export default {
|
||||
}
|
||||
}
|
||||
</script>
|
||||
<style scoped>
|
||||
.background-light {
|
||||
background: #8e9eab;
|
||||
background: -webkit-linear-gradient(to top right, #eeeeee, #c8e8ff);
|
||||
background: linear-gradient(to top right, #ffffff, #c8e8ff);
|
||||
}
|
||||
|
||||
.background-dark {
|
||||
background: #141e30;
|
||||
background: -webkit-linear-gradient(to top left, #243b55, #141e30);
|
||||
background: linear-gradient(to top left, #243b55, #141e30);
|
||||
}
|
||||
</style>
|
||||
+2
-2
@@ -66,8 +66,8 @@
|
||||
</template>
|
||||
<script>
|
||||
import gql from 'graphql-tag'
|
||||
import UserAvatar from '../../components/UserAvatarAuthoriseApp'
|
||||
import UserAvatarIcon from '@/components/UserAvatarIcon'
|
||||
import UserAvatar from '@/cleanup/components/auth/UserAvatarAuthoriseApp'
|
||||
import UserAvatarIcon from '@/cleanup/components/common/UserAvatarIcon'
|
||||
|
||||
export default {
|
||||
name: 'AuthorizeApp',
|
||||
+1
-1
@@ -164,7 +164,7 @@ import gql from 'graphql-tag'
|
||||
import debounce from 'lodash.debounce'
|
||||
import crs from 'crypto-random-string'
|
||||
|
||||
import Strategies from '@/components/auth/Strategies'
|
||||
import Strategies from '@/cleanup/components/auth/Strategies'
|
||||
import { isEmailValid } from '@/auth-helpers'
|
||||
|
||||
export default {
|
||||
@@ -1,65 +0,0 @@
|
||||
<template>
|
||||
<span v-if="pretty">{{ tweeningValue | prettynum(value) }}</span>
|
||||
<span v-else>{{ tweeningValue }}</span>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import TWEEN from "tween";
|
||||
|
||||
export default {
|
||||
name: "AnimatedNumber",
|
||||
props: {
|
||||
value: {
|
||||
type: Number,
|
||||
default: 0
|
||||
},
|
||||
duration: {
|
||||
type: Number,
|
||||
default: 1000
|
||||
},
|
||||
delay: {
|
||||
type: Number,
|
||||
default: 300
|
||||
},
|
||||
pretty: {
|
||||
type: Boolean,
|
||||
default: true
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
tweeningValue: 0
|
||||
};
|
||||
},
|
||||
watch: {
|
||||
value(oldVal, newVal) {
|
||||
this.tween(oldVal, newVal)
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
setTimeout(()=> this.tween(0, this.value), this.delay)
|
||||
},
|
||||
methods: {
|
||||
tween(startValue, endValue) {
|
||||
var vm = this;
|
||||
function animate() {
|
||||
if (TWEEN.update()) {
|
||||
requestAnimationFrame(animate);
|
||||
}
|
||||
}
|
||||
new TWEEN.Tween({ tweeningValue: startValue })
|
||||
.to({ tweeningValue: endValue }, this.duration).easing(TWEEN.Easing.Quintic.Out)
|
||||
.onUpdate(function() {
|
||||
vm.tweeningValue = this.tweeningValue.toFixed(0);
|
||||
})
|
||||
.start();
|
||||
|
||||
animate();
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
|
||||
</style>
|
||||
@@ -1,132 +0,0 @@
|
||||
<template>
|
||||
<div
|
||||
v-if="activity && activity.items.length !== 0"
|
||||
v-tooltip="`Received ${activity.items.length} times. Click to see all commit receives.`"
|
||||
@click="showAllActivityDialog = true"
|
||||
style="margin-right: -10px;"
|
||||
>
|
||||
<div
|
||||
style="cursor: pointer; min-height: 33px; line-height: 33px"
|
||||
:class="`${$vuetify.theme.dark ? 'black' : 'white'} rounded-xl px-2`"
|
||||
>
|
||||
<v-icon small>mdi-call-received</v-icon>
|
||||
|
||||
<user-avatar
|
||||
v-for="userId in receivedUsersUnique.slice(0, 3)"
|
||||
v-show="$vuetify.breakpoint.smAndUp"
|
||||
:id="userId"
|
||||
:key="userId"
|
||||
:show-hover="false"
|
||||
:size="25"
|
||||
></user-avatar>
|
||||
|
||||
<v-avatar
|
||||
v-if="receivedUsersUnique.length > 3"
|
||||
v-show="$vuetify.breakpoint.smAndUp"
|
||||
class="ml-1"
|
||||
size="25"
|
||||
color="primary"
|
||||
>
|
||||
<span class="white--text caption">+{{ receivedUsersUnique.length - 3 }}</span>
|
||||
</v-avatar>
|
||||
<v-avatar v-show="$vuetify.breakpoint.xsOnly" class="ml-1" size="25" color="primary">
|
||||
<span class="white--text caption">{{ receivedUsersUnique.length }}</span>
|
||||
</v-avatar>
|
||||
</div>
|
||||
<v-dialog
|
||||
v-model="showAllActivityDialog"
|
||||
max-width="500"
|
||||
:fullscreen="$vuetify.breakpoint.xsOnly"
|
||||
>
|
||||
<v-card v-if="activity">
|
||||
<v-toolbar>
|
||||
<v-app-bar-nav-icon><v-icon>mdi-download</v-icon></v-app-bar-nav-icon>
|
||||
<v-toolbar-title>All Received Receipts</v-toolbar-title>
|
||||
<v-spacer />
|
||||
<v-btn icon @click="showAllActivityDialog = false"><v-icon>mdi-close</v-icon></v-btn>
|
||||
</v-toolbar>
|
||||
<v-list>
|
||||
<v-list-item v-for="(act, i) in activity.items" :key="i">
|
||||
<v-list-item-icon>
|
||||
<user-avatar :id="act.userId"></user-avatar>
|
||||
</v-list-item-icon>
|
||||
<v-list-item-content>
|
||||
<v-list-item-title class="captionxxx">
|
||||
{{ act.message.split(' was ')[1] }}
|
||||
</v-list-item-title>
|
||||
<v-list-item-subtitle>
|
||||
<timeago :datetime="act.time"></timeago>
|
||||
</v-list-item-subtitle>
|
||||
</v-list-item-content>
|
||||
<v-list-item-action>
|
||||
<source-app-avatar class="mt-3 mb-3" :application-name="act.info.sourceApplication" />
|
||||
</v-list-item-action>
|
||||
</v-list-item>
|
||||
</v-list>
|
||||
</v-card>
|
||||
</v-dialog>
|
||||
</div>
|
||||
</template>
|
||||
<script>
|
||||
import gql from 'graphql-tag'
|
||||
import UserAvatar from './UserAvatar'
|
||||
import SourceAppAvatar from './SourceAppAvatar'
|
||||
|
||||
export default {
|
||||
components: { UserAvatar, SourceAppAvatar },
|
||||
props: {
|
||||
commitId: {
|
||||
type: String,
|
||||
default: null
|
||||
},
|
||||
streamId: {
|
||||
type: String,
|
||||
default: null
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return { showAllActivityDialog: false }
|
||||
},
|
||||
apollo: {
|
||||
activity: {
|
||||
query: gql`
|
||||
query CommitActivity($streamId: String!, $commitId: String!) {
|
||||
stream(id: $streamId) {
|
||||
id
|
||||
commit(id: $commitId) {
|
||||
id
|
||||
activity(actionType: "commit_receive", limit: 200) {
|
||||
items {
|
||||
info
|
||||
time
|
||||
userId
|
||||
message
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
`,
|
||||
update: (data) => data.stream.commit.activity,
|
||||
variables() {
|
||||
return {
|
||||
streamId: this.streamId,
|
||||
commitId: this.commitId
|
||||
}
|
||||
},
|
||||
skip() {
|
||||
if (!this.streamId || !this.commitId) return true
|
||||
return false
|
||||
}
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
receivedUsersUnique() {
|
||||
if (!(this.activity && this.activity.items && this.activity.items.length > 0)) return []
|
||||
let set = new Set()
|
||||
this.activity.items.forEach((item) => set.add(item.userId))
|
||||
return Array.from(set)
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
@@ -276,10 +276,10 @@
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import UserAvatar from './UserAvatar'
|
||||
import UserAvatar from '@/cleanup/components/common/UserAvatar'
|
||||
import UserPill from './UserPill'
|
||||
import SourceAppAvatar from './SourceAppAvatar'
|
||||
import PreviewImage from './PreviewImage'
|
||||
import SourceAppAvatar from '@/cleanup/components/common/SourceAppAvatar'
|
||||
import PreviewImage from '@/cleanup/components/common/PreviewImage'
|
||||
import gql from 'graphql-tag'
|
||||
|
||||
export default {
|
||||
|
||||
@@ -1,70 +0,0 @@
|
||||
<template>
|
||||
<div>
|
||||
<v-list rounded dense>
|
||||
<v-subheader>General</v-subheader>
|
||||
<v-list-item class="elevation-3xxx" link @click="newStreamDialog = true">
|
||||
<v-list-item-icon>
|
||||
<v-icon small class="">mdi-plus-box</v-icon>
|
||||
</v-list-item-icon>
|
||||
<v-list-item-content>
|
||||
<v-list-item-title>New Stream</v-list-item-title>
|
||||
<!-- <v-list-item-subtitle class="caption">
|
||||
Quickly create a new data repository.
|
||||
</v-list-item-subtitle> -->
|
||||
</v-list-item-content>
|
||||
</v-list-item>
|
||||
<v-list-item class="elevation-3xxx" link @click="showServerInviteDialog()">
|
||||
<v-list-item-icon>
|
||||
<v-icon small class="">mdi-email</v-icon>
|
||||
</v-list-item-icon>
|
||||
<v-list-item-content>
|
||||
<v-list-item-title>Send Invite</v-list-item-title>
|
||||
<!-- <v-list-item-subtitle class="caption">
|
||||
Invite a colleague to Speckle!
|
||||
</v-list-item-subtitle> -->
|
||||
</v-list-item-content>
|
||||
</v-list-item>
|
||||
</v-list>
|
||||
|
||||
<server-invite-dialog ref="serverInviteDialog" />
|
||||
|
||||
<v-dialog v-model="newStreamDialog" max-width="500" :fullscreen="$vuetify.breakpoint.xsOnly">
|
||||
<stream-new-dialog
|
||||
:open="newStreamDialog"
|
||||
@created="newStreamDialog = false"
|
||||
@close="newStreamDialog = false"
|
||||
/>
|
||||
</v-dialog>
|
||||
</div>
|
||||
</template>
|
||||
<script>
|
||||
export default {
|
||||
components: {
|
||||
ServerInviteDialog: () => import('@/components/dialogs/ServerInviteDialog'),
|
||||
StreamNewDialog: () => import('@/components/dialogs/StreamNewDialog'),
|
||||
SearchBar: () => import('@/components/SearchBar')
|
||||
},
|
||||
props: {
|
||||
OpenNewStream: {
|
||||
type: Number,
|
||||
default: 0
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
newStreamDialog: false
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
OpenNewStream(val, old) {
|
||||
this.newStreamDialog = true
|
||||
}
|
||||
},
|
||||
mounted() {},
|
||||
methods: {
|
||||
showServerInviteDialog() {
|
||||
this.$refs.serverInviteDialog.show()
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
@@ -1,78 +0,0 @@
|
||||
<template>
|
||||
<section-card expandable class="my-2" dense>
|
||||
<template #header>Access Tokens</template>
|
||||
<template #actions>
|
||||
<v-spacer />
|
||||
<v-btn small color="primary" @click="tokenDialog = true">new token</v-btn>
|
||||
</template>
|
||||
<v-card rounded="lg" :class="`${!$vuetify.theme.dark ? 'grey lighten-5' : ''}`">
|
||||
<v-card-text>
|
||||
Personal Access Tokens can be used to access the Speckle API on this server; they function
|
||||
like ordinary OAuth access tokens. Use them in your scripts or apps!
|
||||
<b>
|
||||
Treat them like a password: do not post them anywhere where they could be accessed by
|
||||
others (e.g., public repos).
|
||||
</b>
|
||||
</v-card-text>
|
||||
<v-card-text v-if="$apollo.loading">Loading...</v-card-text>
|
||||
<v-card-text v-if="tokens && tokens.length != 0">
|
||||
<v-list three-line>
|
||||
<list-item-token
|
||||
v-for="token in tokens"
|
||||
:key="token.id"
|
||||
:token="token"
|
||||
@deleted="refreshList"
|
||||
/>
|
||||
</v-list>
|
||||
</v-card-text>
|
||||
<v-card-text v-else>You have no api tokens.</v-card-text>
|
||||
|
||||
<v-dialog v-model="tokenDialog" width="500">
|
||||
<token-dialog @token-added="refreshList" @close="tokenDialog = false" />
|
||||
</v-dialog>
|
||||
</v-card>
|
||||
</section-card>
|
||||
</template>
|
||||
<script>
|
||||
import gql from 'graphql-tag'
|
||||
import ListItemToken from './ListItemPersonalAccessToken'
|
||||
import TokenDialog from './dialogs/TokenDialog'
|
||||
|
||||
export default {
|
||||
components: {
|
||||
ListItemToken,
|
||||
TokenDialog,
|
||||
SectionCard: () => import('@/cleanup/components/common/SectionCard')
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
tokenDialog: false
|
||||
}
|
||||
},
|
||||
apollo: {
|
||||
tokens: {
|
||||
query: gql`
|
||||
query {
|
||||
user {
|
||||
id
|
||||
apiTokens {
|
||||
id
|
||||
name
|
||||
lastUsed
|
||||
lastChars
|
||||
createdAt
|
||||
scopes
|
||||
}
|
||||
}
|
||||
}
|
||||
`,
|
||||
update: (data) => data.user.apiTokens
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
refreshList() {
|
||||
this.$apollo.queries.tokens.refetch()
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
@@ -1,129 +0,0 @@
|
||||
<template>
|
||||
<v-card class="elevation-0 mt-3 mb-5 transparent">
|
||||
<v-card-title>Your Apps</v-card-title>
|
||||
<v-card-text class="">
|
||||
Here you can review the apps that you have granted access to.
|
||||
<v-btn
|
||||
v-if="!hasManager"
|
||||
plain
|
||||
small
|
||||
:href="`speckle://accounts?add_server_account=${rootUrl}`"
|
||||
>
|
||||
Add Account To Desktop Manager
|
||||
</v-btn>
|
||||
</v-card-text>
|
||||
<v-card-text v-if="$apollo.loading">Loading...</v-card-text>
|
||||
<v-card-text v-if="authorizedApps && authorizedApps.length !== 0">
|
||||
<v-row>
|
||||
<v-col
|
||||
v-for="app in authorizedApps"
|
||||
:key="app.id"
|
||||
cols="12"
|
||||
sm="6"
|
||||
class="d-flex"
|
||||
style="flex-direction: column"
|
||||
>
|
||||
<v-card flex-grow-1 d-flex flex-column>
|
||||
<v-card-text>
|
||||
<h3 class="mb-3">
|
||||
<v-icon v-if="app.trustByDefault" class="mr-1 primary--text" small>
|
||||
mdi-shield
|
||||
</v-icon>
|
||||
{{ app.name }}
|
||||
</h3>
|
||||
<p class="text-truncate">
|
||||
{{ app.description }}
|
||||
<span v-show="app.id === 'spklwebapp'" class="caption">
|
||||
(This is the app your're currently using!)
|
||||
</span>
|
||||
</p>
|
||||
</v-card-text>
|
||||
<v-spacer></v-spacer>
|
||||
<v-card-actions>
|
||||
<v-btn plain small color="error" @click="showRevokeAccessDialog(app)">
|
||||
Revoke access
|
||||
</v-btn>
|
||||
<v-btn plain small :href="app.redirectUrl" target="_blank">Open</v-btn>
|
||||
</v-card-actions>
|
||||
</v-card>
|
||||
</v-col>
|
||||
</v-row>
|
||||
<v-dialog v-model="showRevokeDialog" max-width="500">
|
||||
<v-card>
|
||||
<v-card-title>Revoke Access</v-card-title>
|
||||
<v-card-text>
|
||||
Revoking access to your app will log you out of it on all devices. Are you sure you want
|
||||
to proceed?
|
||||
</v-card-text>
|
||||
<v-card-actions>
|
||||
<v-spacer />
|
||||
<v-btn plain small color="error" @click="revokeAccess()">Revoke access</v-btn>
|
||||
<v-btn plain small>Cancel</v-btn>
|
||||
</v-card-actions>
|
||||
</v-card>
|
||||
</v-dialog>
|
||||
</v-card-text>
|
||||
</v-card>
|
||||
</template>
|
||||
<script>
|
||||
import gql from 'graphql-tag'
|
||||
|
||||
export default {
|
||||
components: {},
|
||||
data() {
|
||||
return {
|
||||
appDialog: false,
|
||||
showRevokeDialog: false,
|
||||
appToRevoke: null
|
||||
}
|
||||
},
|
||||
apollo: {
|
||||
authorizedApps: {
|
||||
query: gql`
|
||||
query {
|
||||
user {
|
||||
id
|
||||
authorizedApps {
|
||||
id
|
||||
description
|
||||
name
|
||||
redirectUrl
|
||||
trustByDefault
|
||||
}
|
||||
}
|
||||
}
|
||||
`,
|
||||
update: (data) => data.user.authorizedApps.filter((app) => app.id !== 'spklwebapp')
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
rootUrl() {
|
||||
return window.location.origin
|
||||
},
|
||||
hasManager() {
|
||||
if (!this.authorizedApps) return null
|
||||
return this.authorizedApps.findIndex((a) => a.id === 'sdm') !== -1
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
showRevokeAccessDialog(app) {
|
||||
this.showRevokeDialog = true
|
||||
this.appToRevoke = app
|
||||
},
|
||||
async revokeAccess() {
|
||||
this.showRevokeDialog = false
|
||||
this.$matomo && this.$matomo.trackPageView('user/app/revoke')
|
||||
this.$mixpanel.track('App Action', { type: 'action', name: 'revoke', hostApp: 'web' })
|
||||
await this.$apollo.mutate({
|
||||
mutation: gql`
|
||||
mutation{ appRevokeAccess(appId: "${this.appToRevoke.id}")}
|
||||
`
|
||||
})
|
||||
this.refreshList()
|
||||
},
|
||||
refreshList() {
|
||||
this.$apollo.queries.authorizedApps.refetch()
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
@@ -1,80 +0,0 @@
|
||||
<template>
|
||||
<div>
|
||||
<div v-if="!user">
|
||||
<v-skeleton-loader type="card"></v-skeleton-loader>
|
||||
</div>
|
||||
<div v-else>
|
||||
<v-card
|
||||
rounded="lg"
|
||||
:class="`${!$vuetify.theme.dark ? 'grey lighten-5' : ''} mt-5`"
|
||||
style="overflow: hidden"
|
||||
>
|
||||
<v-toolbar flat class="error--text" dark dense>
|
||||
<v-toolbar-title>Delete Account</v-toolbar-title>
|
||||
<v-spacer></v-spacer>
|
||||
<v-btn icon @click="showDelete = !showDelete"><v-icon>mdi-chevron-down</v-icon></v-btn>
|
||||
</v-toolbar>
|
||||
<div v-show="showDelete">
|
||||
<v-card-text>
|
||||
This action cannot be undone. We will delete all streams where you are the sole owner,
|
||||
and any associated data.
|
||||
</v-card-text>
|
||||
<v-card-actions>
|
||||
<v-btn block @click="deleteUser">Delete account</v-btn>
|
||||
</v-card-actions>
|
||||
</div>
|
||||
</v-card>
|
||||
<user-delete-dialog ref="userDeleteDialog"></user-delete-dialog>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
<script>
|
||||
import gql from 'graphql-tag'
|
||||
import UserDeleteDialog from '../components/dialogs/UserDeleteDialog'
|
||||
import { signOut } from '@/auth-helpers'
|
||||
|
||||
export default {
|
||||
components: { UserDeleteDialog },
|
||||
props: {
|
||||
user: {
|
||||
type: Object,
|
||||
default: null
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
showDelete: false
|
||||
}
|
||||
},
|
||||
computed: {},
|
||||
methods: {
|
||||
deleteUser() {
|
||||
this.$refs.userDeleteDialog.open(this.user).then((dialog) => {
|
||||
if (!dialog.result) return
|
||||
|
||||
this.$matomo && this.$matomo.trackPageView('user/delete')
|
||||
this.$mixpanel.track('User Action', { type: 'action', name: 'delete', hostApp: 'web' })
|
||||
|
||||
this.isLoading = true
|
||||
this.$apollo
|
||||
.mutate({
|
||||
mutation: gql`
|
||||
mutation userDelete($myUserConfirmation: UserDeleteInput!) {
|
||||
userDelete(userConfirmation: $myUserConfirmation)
|
||||
}
|
||||
`,
|
||||
variables: {
|
||||
myUserConfirmation: {
|
||||
email: dialog.email
|
||||
}
|
||||
}
|
||||
})
|
||||
.then(() => {
|
||||
this.isLoading = false
|
||||
signOut(this.$mixpanel)
|
||||
})
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
@@ -1,157 +0,0 @@
|
||||
<template>
|
||||
<div>
|
||||
<v-dialog v-model="avatarDialog" max-width="400">
|
||||
<v-card>
|
||||
<v-card-title>Choose a new profile picture</v-card-title>
|
||||
<v-card-text class="pl-10 pr-0 mt-5">
|
||||
<v-image-input
|
||||
v-model="imageData"
|
||||
:image-quality="0.85"
|
||||
:image-height="256"
|
||||
:image-width="256"
|
||||
full-width
|
||||
full-height
|
||||
clearable
|
||||
image-format="jpeg"
|
||||
/>
|
||||
</v-card-text>
|
||||
<v-card-actions>
|
||||
<span v-if="imageData" class="caption">You look wonderful!</span>
|
||||
<v-spacer></v-spacer>
|
||||
<v-btn text @click="avatarDialog = false">cancel</v-btn>
|
||||
<v-btn :disabled="!imageData" @click="updateAvatar">Save</v-btn>
|
||||
</v-card-actions>
|
||||
</v-card>
|
||||
</v-dialog>
|
||||
|
||||
<div v-if="!user">
|
||||
<v-skeleton-loader type="card"></v-skeleton-loader>
|
||||
</div>
|
||||
|
||||
<v-card
|
||||
v-else
|
||||
class="elevation-0"
|
||||
rounded="lg"
|
||||
:class="`${!$vuetify.theme.dark ? 'grey lighten-5' : ''}`"
|
||||
>
|
||||
<v-toolbar flat :class="`${!$vuetify.theme.dark ? 'grey lighten-4' : ''}`">
|
||||
<v-toolbar-title>
|
||||
<span v-if="isSelf">Hi</span>
|
||||
{{ user.name }}
|
||||
<span v-if="isSelf">!</span>
|
||||
</v-toolbar-title>
|
||||
<v-spacer></v-spacer>
|
||||
<v-btn v-if="isSelf" small rounded color="primary" @click="editUser">
|
||||
<v-icon small class="mr-2">mdi-cog-outline</v-icon>
|
||||
Edit
|
||||
</v-btn>
|
||||
</v-toolbar>
|
||||
<v-row class="pa-4" align="stretch">
|
||||
<v-col cols="12" sm="8">
|
||||
<p v-if="user.company" class="subtitle-1">Company: {{ user.company }}</p>
|
||||
<p v-if="user.email && isSelf">Email: {{ user.email }}</p>
|
||||
<p v-if="user.bio">Bio: {{ user.bio }}</p>
|
||||
<p v-else>This user keeps an air of mystery around themselves.</p>
|
||||
|
||||
<span v-if="isSelf" class="caption">ID: {{ user.id }}</span>
|
||||
<br />
|
||||
</v-col>
|
||||
<v-col cols="12" sm="4" class="d-flex justify-center">
|
||||
<div @click="avatarDialog = isSelf ? true : false">
|
||||
<user-avatar-icon
|
||||
v-tooltip="`${isSelf ? 'Change your profile picture' : ''}`"
|
||||
:style="`${isSelf ? 'cursor: pointer;' : ''}`"
|
||||
:size="100"
|
||||
:avatar="user.avatar"
|
||||
:seed="user.id"
|
||||
></user-avatar-icon>
|
||||
</div>
|
||||
</v-col>
|
||||
</v-row>
|
||||
<v-card-actions></v-card-actions>
|
||||
|
||||
<user-edit-dialog ref="userDialog" :user="user"></user-edit-dialog>
|
||||
</v-card>
|
||||
</div>
|
||||
</template>
|
||||
<script>
|
||||
import gql from 'graphql-tag'
|
||||
import UserEditDialog from '../components/dialogs/UserEditDialog'
|
||||
import VImageInput from 'vuetify-image-input/a-la-carte'
|
||||
import UserAvatarIcon from '@/components/UserAvatarIcon'
|
||||
|
||||
export default {
|
||||
components: { UserAvatarIcon, UserEditDialog, VImageInput },
|
||||
props: {
|
||||
user: {
|
||||
type: Object,
|
||||
default: null
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
avatarDialog: false,
|
||||
imageData: null
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
isSelf() {
|
||||
if (!this.user) return false
|
||||
return this.user.id === localStorage.getItem('uuid')
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
async updateAvatar() {
|
||||
try {
|
||||
await this.$apollo.mutate({
|
||||
mutation: gql`
|
||||
mutation userUpdate($update: UserUpdateInput!) {
|
||||
userUpdate(user: $update)
|
||||
}
|
||||
`,
|
||||
variables: {
|
||||
update: {
|
||||
avatar: this.imageData
|
||||
}
|
||||
}
|
||||
})
|
||||
this.$emit('update')
|
||||
} catch (e) {
|
||||
console.log(e)
|
||||
}
|
||||
|
||||
this.avatarDialog = false
|
||||
},
|
||||
//using vue dialogs just like .net modals
|
||||
async editUser() {
|
||||
this.$refs.userDialog.open(this.user).then((dialog) => {
|
||||
if (!dialog.result) return
|
||||
|
||||
this.$matomo && this.$matomo.trackPageView('user/update')
|
||||
this.$mixpanel.track('User Action', { type: 'action', name: 'update', hostApp: 'web' })
|
||||
|
||||
this.isLoading = true
|
||||
this.$apollo
|
||||
.mutate({
|
||||
mutation: gql`
|
||||
mutation userUpdate($myUser: UserUpdateInput!) {
|
||||
userUpdate(user: $myUser)
|
||||
}
|
||||
`,
|
||||
variables: {
|
||||
myUser: {
|
||||
name: dialog.user.name,
|
||||
bio: dialog.user.bio,
|
||||
company: dialog.user.company
|
||||
}
|
||||
}
|
||||
})
|
||||
.then(() => {
|
||||
this.isLoading = false
|
||||
this.$emit('update')
|
||||
})
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
@@ -8,7 +8,7 @@ const routes = [
|
||||
path: '/authn',
|
||||
name: 'Auth',
|
||||
redirect: '/authn/login',
|
||||
component: () => import('@/views/Auth.vue'),
|
||||
component: () => import('@/cleanup/pages/auth/Auth.vue'),
|
||||
children: [
|
||||
{
|
||||
path: 'login',
|
||||
@@ -16,7 +16,7 @@ const routes = [
|
||||
meta: {
|
||||
title: 'Login | Speckle'
|
||||
},
|
||||
component: () => import('@/views/auth/Login.vue')
|
||||
component: () => import('@/cleanup/pages/auth/Login.vue')
|
||||
},
|
||||
{
|
||||
path: 'register',
|
||||
@@ -24,7 +24,7 @@ const routes = [
|
||||
meta: {
|
||||
title: 'Register | Speckle'
|
||||
},
|
||||
component: () => import('@/views/auth/Registration.vue')
|
||||
component: () => import('@/cleanup/pages/auth/Registration.vue')
|
||||
},
|
||||
{
|
||||
path: 'resetpassword',
|
||||
@@ -32,7 +32,7 @@ const routes = [
|
||||
meta: {
|
||||
title: 'Register | Speckle'
|
||||
},
|
||||
component: () => import('@/views/auth/ResetPasswordRequest.vue')
|
||||
component: () => import('@/cleanup/pages/auth/ResetPasswordRequest.vue')
|
||||
},
|
||||
{
|
||||
path: 'resetpassword/finalize',
|
||||
@@ -40,7 +40,7 @@ const routes = [
|
||||
meta: {
|
||||
title: 'Register | Speckle'
|
||||
},
|
||||
component: () => import('@/views/auth/ResetPasswordFinalization.vue')
|
||||
component: () => import('@/cleanup/pages/auth/ResetPasswordFinalization.vue')
|
||||
},
|
||||
{
|
||||
path: 'verify/:appId/:challenge',
|
||||
@@ -48,7 +48,7 @@ const routes = [
|
||||
meta: {
|
||||
title: 'Authorizing App | Speckle'
|
||||
},
|
||||
component: () => import('@/views/auth/AuthorizeApp.vue')
|
||||
component: () => import('@/cleanup/pages/auth/AuthorizeApp.vue')
|
||||
}
|
||||
]
|
||||
},
|
||||
|
||||
Reference in New Issue
Block a user