- Notification bell with invites (accept/decline refreshes workspaces) and unread badge (polling + socket push); team chat view realtime - Avatar/background uploads in settings + glassmorphism widgets toggle; background layer resolves /uploads URLs; full-bleed blurred backdrop - Sidebar: visible creation errors, working sub-pages (parent_id), TEAM badge workspaces, refresh button, chat nav; admin creates users - Tasks: per-team boards, assignee select with avatars; admin tabs for users/teams/groups (team workspaces, permission checkboxes) - Register view removed (admin-only accounts); login localized; prettier emoji set; scaling/overflow + mobile fixes
57 lines
1.6 KiB
Vue
57 lines
1.6 KiB
Vue
<script setup>
|
||
import { ref } from 'vue';
|
||
import api from '../api';
|
||
import { useLocaleStore } from '../stores/locale';
|
||
import UserSearch from './UserSearch.vue';
|
||
|
||
const props = defineProps({ workspaceId: String });
|
||
const emit = defineEmits(['invited']);
|
||
const L = useLocaleStore();
|
||
const email = ref('');
|
||
const picked = ref(null);
|
||
const role = ref('member');
|
||
const msg = ref('');
|
||
const err = ref('');
|
||
|
||
function onPick(u) {
|
||
picked.value = u;
|
||
email.value = u.email;
|
||
}
|
||
|
||
async function invite() {
|
||
msg.value = '';
|
||
err.value = '';
|
||
const target = email.value.trim();
|
||
if (!target) return;
|
||
try {
|
||
await api.post(`/workspaces/${props.workspaceId}/members`, { email: target, role: role.value });
|
||
msg.value = `✨ ${target} → ${role.value}`;
|
||
email.value = '';
|
||
picked.value = null;
|
||
emit('invited');
|
||
} catch (e) {
|
||
err.value = e.response?.data?.error || 'Invite failed';
|
||
}
|
||
}
|
||
</script>
|
||
|
||
<template>
|
||
<div class="card">
|
||
<h4>{{ L.t('inv.title') }}</h4>
|
||
<p class="muted">{{ L.t('inv.hint') }}</p>
|
||
<UserSearch @pick="onPick" />
|
||
<div class="row" style="margin-top: 8px">
|
||
<input v-model="email" placeholder="teammate@example.com" />
|
||
<select v-model="role" style="max-width: 130px">
|
||
<option value="viewer">viewer</option>
|
||
<option value="member">member</option>
|
||
<option value="admin">admin</option>
|
||
</select>
|
||
<button @click="invite">✉️ {{ L.t('inv.btn') }}</button>
|
||
</div>
|
||
<p v-if="picked" class="muted">🔎 {{ picked.name }} <{{ picked.email }}></p>
|
||
<p v-if="msg" style="color: green">{{ msg }}</p>
|
||
<p v-if="err" style="color: red">{{ err }}</p>
|
||
</div>
|
||
</template>
|