<template> <div class="page"> <div class="profile"> <div class="profile-header"> <div class="user"> <img v-if="profile.avatar" :src="profile.avatar" class="avatar" > <h2 class="username ellipsis">{{ profile.username }}</h2> </div> <span class="age">{{ formatDistanceStrict(Date.now(), profile.createdAt) }}</span> </div> <section class="profile-section"> <div class="section-header"> <h3 class="heading">Stashes</h3> <button class="button" @click="showStashDialog = true" > <Icon icon="plus3" /> <span class="button-label">New stash</span> </button> </div> <Dialog v-if="showStashDialog" title="New stash" @close="showStashDialog = false" @open="stashNameInput?.focus()" > <form class="dialog-body" @submit.prevent="createStash" > <input ref="stashNameInput" v-model="stashName" maxlength="24" placeholder="Stash name" class="input" > <button class="button button-submit" >Create stash</button> </form> </Dialog> <ul class="stashes nolist"> <li v-for="stash in profile.stashes" :key="`stash-${stash.id}`" > <StashTile :stash="stash" :profile="profile" @reload="reloadProfile" /> </li> </ul> </section> </div> </div> </template> <script setup> import { ref, inject } from 'vue'; import { formatDistanceStrict } from 'date-fns'; import { get, post } from '#/src/api.js'; import StashTile from '#/components/stashes/tile.vue'; import Dialog from '#/components/dialog/dialog.vue'; const pageContext = inject('pageContext'); const profile = ref(pageContext.pageProps.profile); const stashName = ref(null); const stashNameInput = ref(null); const done = ref(true); const showStashDialog = ref(false); async function reloadProfile() { profile.value = await get(`/users/${profile.value.id}`); } async function createStash() { if (done.value === false) { return; } done.value = false; await post('/stashes', { name: stashName.value, public: false, }, { successFeedback: `Created stash '${stashName.value}'`, errorFeedback: `Failed to create stash '${stashName.value}'`, appendErrorMessage: true, }).finally(() => { done.value = true; }); showStashDialog.value = false; stashName.value = null; await reloadProfile(); } </script> <style scoped> .page { display: flex; flex-grow: 1; justify-content: center; background: var(--background-base-10); } .profile { width: 1200px; max-width: 100%; } .profile-header { display: flex; justify-content: space-between; align-items: center; padding: .5rem 1rem; color: var(--highlight-strong-30); background: var(--grey-dark-40); border-radius: 0 0 .5rem .5rem; } .user { display: flex; overflow: hidden; } .username { margin: 0; font-size: 1.25rem; } .age { display: flex; flex-shrink: 0; font-size: .9rem; .icon { width: .9rem; fill: var(--highlight-strong-20); margin-right: .75rem; transform: translateY(-1px); } } .avatar { width: 1.5rem; height: 1.5rem; border-radius: .25rem; margin-right: 1rem; } .section-header { display: flex; align-items: center; justify-content: space-between; padding: .5rem .5rem .5rem .5rem; .button { margin-left: 1rem; } } .heading { margin: 0; font-size: 1.1rem; color: var(--primary); } .stashes { display: grid; grid-template-columns: repeat(auto-fill, minmax(12rem, 1fr)); gap: 1rem; padding: 0 .5rem 1rem .5rem; } .dialog-body { padding: 1rem; .input { margin-bottom: .5rem; } } @media(--compact) { .profile-header { border-radius: 0; } .section-header { padding: .5rem 1rem .5rem 1rem; } .stashes { padding: 0 1rem 1rem 1rem; } } @media(--small-30) { .section-header { padding: .5rem .5rem .5rem .5rem; } .stashes { padding: 0 .5rem 1rem .5rem; } .age .icon { display: none; } } @media(--small-50) { .age { display: none; } } </style>