traxxx-web/pages/users/@username/+Page.vue

394 lines
7.1 KiB
Vue
Raw Normal View History

2024-02-29 04:08:54 +00:00
<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
v-if="profile.id === user?.id"
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>
<section
v-if="profile.id === user?.id"
class="profile-section"
>
<div class="section-header">
<h3 class="heading">Alerts</h3>
<button
class="button"
@click="showAlertDialog = true"
>
<Icon icon="plus3" />
<span class="button-label">New alert</span>
</button>
</div>
<ul class="alerts nolist">
<li
v-for="alert in alerts"
:key="`alert-${alert.id}`"
class="alert"
>
<div class="alert-details">
<span
v-if="alert.tags.length > 0"
class="alert-tags"
><a
v-for="tag in alert.tags"
:key="`tag-${alert.id}-${tag.id}`"
:href="`/tag/${tag.slug}`"
class="link"
>{{ tag.name }}</a>&nbsp;</span>
<span
v-if="alert.actors.length > 0"
class="alert-actors"
>with <a
v-for="actor in alert.actors"
:key="`actor-${alert.id}-${actor.id}`"
:href="`/actor/${actor.id}/${actor.slug}`"
class="link"
>{{ actor.name }}</a>&nbsp;</span>
<span
v-if="alert.entities.length > 0"
class="alert-entities"
>for <a
v-for="entity in alert.entities"
:key="`entity-${alert.id}-${entity.id}`"
:href="`/${entity.type}/${entity.slug}`"
class="link"
>{{ entity.name }}</a>&nbsp;</span>
<span
v-if="alert.matches.length > 0"
class="alert-entities"
>matching {{ alert.matches.map((match) => match.expression).join(', ') }}</span>
</div>
<div class="alert-actions">
<Icon
icon="bin"
@click="removeAlert(alert)"
/>
</div>
</li>
</ul>
<AlertDialog
v-if="showAlertDialog"
@close="showAlertDialog = false; reloadAlerts();"
/>
</section>
</div>
2024-02-29 04:08:54 +00:00
</div>
</template>
<script setup>
import { ref, inject } from 'vue';
import { formatDistanceStrict } from 'date-fns';
import { get, post, del } from '#/src/api.js';
2024-02-29 04:08:54 +00:00
import StashTile from '#/components/stashes/tile.vue';
import Dialog from '#/components/dialog/dialog.vue';
import AlertDialog from '#/components/alerts/create.vue';
2024-02-29 04:08:54 +00:00
const pageContext = inject('pageContext');
const user = pageContext.user;
const profile = ref(pageContext.pageProps.profile);
const alerts = ref(pageContext.pageProps.alerts);
const stashName = ref(null);
const stashNameInput = ref(null);
const done = ref(true);
const showStashDialog = ref(false);
const showAlertDialog = ref(false);
async function reloadProfile() {
profile.value = await get(`/users/${profile.value.id}`);
}
async function createStash() {
if (done.value === false) {
return;
}
done.value = false;
try {
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();
}
async function reloadAlerts() {
alerts.value = await get('/alerts');
}
async function removeAlert(alert) {
if (done.value === false) {
return;
}
done.value = false;
const alertLabel = [
...alert.actors.map((actor) => actor.name),
...alert.tags.map((tag) => tag.name),
...alert.entities.map((entity) => entity.name),
...alert.matches.map((match) => match.expression),
].filter(Boolean).join(', ');
try {
await del(`/alerts/${alert.id}`, {
undoFeedback: `Removed alert for '${alertLabel}'`,
errorFeedback: `Failed to remove alert for '${alertLabel}'`,
appendErrorMessage: true,
});
await reloadAlerts();
} finally {
done.value = true;
}
}
2024-02-29 04:08:54 +00:00
</script>
<style scoped>
.page {
display: flex;
flex-grow: 1;
justify-content: center;
background: var(--background-base-10);
}
.profile {
width: 1200px;
max-width: 100%;
}
2024-02-29 04:08:54 +00:00
.profile-header {
display: flex;
justify-content: space-between;
2024-02-29 04:08:54 +00:00
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;
2024-02-29 04:08:54 +00:00
}
.username {
margin: 0;
font-size: 1.25rem;
2024-02-29 04:08:54 +00:00
}
.age {
display: flex;
flex-shrink: 0;
font-size: .9rem;
.icon {
width: .9rem;
fill: var(--highlight-strong-20);
margin-right: .75rem;
transform: translateY(-1px);
}
}
2024-02-29 04:08:54 +00:00
.avatar {
width: 1.5rem;
height: 1.5rem;
2024-02-29 04:08:54 +00:00
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));
2024-03-17 04:27:43 +00:00
gap: 1rem;
padding: 0 .5rem 1rem .5rem;
}
.alerts {
width: 100%;
}
.alert {
display: flex;
align-items: stretch;
&:not(:last-child) {
border-bottom: solid 1px var(--shadow-weak-40);
}
&:hover {
background: var(--shadow-weak-40);
}
2024-05-27 01:12:38 +00:00
.link:not(:last-child):after {
content: ', ';
}
}
.alert-details {
padding: .5rem 1rem;
flex-grow: 1;
line-height: 1.5;
.link {
color: inherit;
}
}
.alert-actions {
.icon {
height: 100%;
padding: 0 .5rem;
fill: var(--shadow);
&:hover {
cursor: pointer;
fill: var(--primary);
}
}
}
.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;
}
}
2024-02-29 04:08:54 +00:00
</style>