chore(frontend): introduce prettier + eslint and prune unused code
- Add Prettier 3 with prettier-plugin-organize-imports (sorts/removes unused imports) - Add ESLint 10 flat config with typescript-eslint + eslint-plugin-vue + eslint-config-prettier - Add root scripts: format, format:check, lint, lint:fix - Reformat 257 files across admin-web, ops-web, desktop-client, packages - Remove unused locals/exports flagged by --noUnusedLocals/--noUnusedParameters - Fix duplicate localTabLabel key, surrogate-pair regex u-flag, NBSP literal in regex - Skip server/ (Go) and apps/browser-extension/ (deprecated per ADR) Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -1,136 +1,144 @@
|
||||
<script setup lang="ts">
|
||||
import { computed, ref } from "vue";
|
||||
import { useI18n } from "vue-i18n";
|
||||
import { useMutation, useQueryClient } from "@tanstack/vue-query";
|
||||
import { message } from "ant-design-vue";
|
||||
import { EditOutlined, UserOutlined, LoadingOutlined } from "@ant-design/icons-vue";
|
||||
import type { KolProfile } from "@geo/shared-types";
|
||||
import { EditOutlined, LoadingOutlined, UserOutlined } from '@ant-design/icons-vue'
|
||||
import type { KolProfile } from '@geo/shared-types'
|
||||
import { useMutation, useQueryClient } from '@tanstack/vue-query'
|
||||
import { message } from 'ant-design-vue'
|
||||
import { computed, ref } from 'vue'
|
||||
import { useI18n } from 'vue-i18n'
|
||||
|
||||
import { kolManageApi, resolveApiURL } from "@/lib/api";
|
||||
import { formatError } from "@/lib/errors";
|
||||
import { kolManageApi, resolveApiURL } from '@/lib/api'
|
||||
import { formatError } from '@/lib/errors'
|
||||
|
||||
const props = defineProps<{
|
||||
profile: KolProfile;
|
||||
}>();
|
||||
profile: KolProfile
|
||||
}>()
|
||||
|
||||
const { t } = useI18n();
|
||||
const queryClient = useQueryClient();
|
||||
const { t } = useI18n()
|
||||
const queryClient = useQueryClient()
|
||||
|
||||
const editing = ref<"display_name" | "bio" | null>(null);
|
||||
const draftDisplayName = ref(props.profile.display_name);
|
||||
const draftBio = ref(props.profile.bio ?? "");
|
||||
const avatarUploading = ref(false);
|
||||
const fileInput = ref<HTMLInputElement | null>(null);
|
||||
const editing = ref<'display_name' | 'bio' | null>(null)
|
||||
const draftDisplayName = ref(props.profile.display_name)
|
||||
const draftBio = ref(props.profile.bio ?? '')
|
||||
const avatarUploading = ref(false)
|
||||
const fileInput = ref<HTMLInputElement | null>(null)
|
||||
|
||||
const avatarURL = computed(() => resolveApiURL(props.profile.avatar_url));
|
||||
const avatarURL = computed(() => resolveApiURL(props.profile.avatar_url))
|
||||
|
||||
const updateMutation = useMutation({
|
||||
mutationFn: kolManageApi.updateProfile,
|
||||
onSuccess: async () => {
|
||||
message.success(t("kol.profile.persona.saveSuccess"));
|
||||
message.success(t('kol.profile.persona.saveSuccess'))
|
||||
await Promise.all([
|
||||
queryClient.invalidateQueries({ queryKey: ["kol", "profile"] }),
|
||||
queryClient.invalidateQueries({ queryKey: ["workspace", "kol-cards"] }),
|
||||
queryClient.invalidateQueries({ queryKey: ["kol", "packages"] }),
|
||||
]);
|
||||
editing.value = null;
|
||||
queryClient.invalidateQueries({ queryKey: ['kol', 'profile'] }),
|
||||
queryClient.invalidateQueries({ queryKey: ['workspace', 'kol-cards'] }),
|
||||
queryClient.invalidateQueries({ queryKey: ['kol', 'packages'] }),
|
||||
])
|
||||
editing.value = null
|
||||
},
|
||||
onError: (error) => {
|
||||
message.error(formatError(error) || t("kol.profile.persona.saveError"));
|
||||
draftDisplayName.value = props.profile.display_name;
|
||||
draftBio.value = props.profile.bio ?? "";
|
||||
message.error(formatError(error) || t('kol.profile.persona.saveError'))
|
||||
draftDisplayName.value = props.profile.display_name
|
||||
draftBio.value = props.profile.bio ?? ''
|
||||
},
|
||||
});
|
||||
})
|
||||
|
||||
function saveFields(patch: { display_name?: string; bio?: string | null; avatar_url?: string | null }) {
|
||||
function saveFields(patch: {
|
||||
display_name?: string
|
||||
bio?: string | null
|
||||
avatar_url?: string | null
|
||||
}) {
|
||||
updateMutation.mutate({
|
||||
display_name: patch.display_name ?? props.profile.display_name,
|
||||
bio: patch.bio !== undefined ? patch.bio : props.profile.bio,
|
||||
avatar_url: patch.avatar_url !== undefined ? patch.avatar_url : props.profile.avatar_url,
|
||||
});
|
||||
})
|
||||
}
|
||||
|
||||
function commitDisplayName() {
|
||||
const next = draftDisplayName.value.trim();
|
||||
const next = draftDisplayName.value.trim()
|
||||
if (!next) {
|
||||
message.error(t("kol.profile.persona.displayNameRequired"));
|
||||
return;
|
||||
message.error(t('kol.profile.persona.displayNameRequired'))
|
||||
return
|
||||
}
|
||||
if (next.length > 40) {
|
||||
message.error(t("kol.profile.persona.displayNameTooLong"));
|
||||
return;
|
||||
message.error(t('kol.profile.persona.displayNameTooLong'))
|
||||
return
|
||||
}
|
||||
if (next === props.profile.display_name) {
|
||||
editing.value = null;
|
||||
return;
|
||||
editing.value = null
|
||||
return
|
||||
}
|
||||
saveFields({ display_name: next });
|
||||
saveFields({ display_name: next })
|
||||
}
|
||||
|
||||
function commitBio() {
|
||||
const next = draftBio.value.trim();
|
||||
const next = draftBio.value.trim()
|
||||
if (next.length > 500) {
|
||||
message.error(t("kol.profile.persona.bioTooLong"));
|
||||
return;
|
||||
message.error(t('kol.profile.persona.bioTooLong'))
|
||||
return
|
||||
}
|
||||
const nextVal = next === "" ? null : next;
|
||||
const nextVal = next === '' ? null : next
|
||||
if (nextVal === (props.profile.bio ?? null)) {
|
||||
editing.value = null;
|
||||
return;
|
||||
editing.value = null
|
||||
return
|
||||
}
|
||||
saveFields({ bio: nextVal });
|
||||
saveFields({ bio: nextVal })
|
||||
}
|
||||
|
||||
function cancelEdit() {
|
||||
draftDisplayName.value = props.profile.display_name;
|
||||
draftBio.value = props.profile.bio ?? "";
|
||||
editing.value = null;
|
||||
draftDisplayName.value = props.profile.display_name
|
||||
draftBio.value = props.profile.bio ?? ''
|
||||
editing.value = null
|
||||
}
|
||||
|
||||
function startDisplayNameEdit() {
|
||||
draftDisplayName.value = props.profile.display_name;
|
||||
editing.value = "display_name";
|
||||
draftDisplayName.value = props.profile.display_name
|
||||
editing.value = 'display_name'
|
||||
}
|
||||
|
||||
function startBioEdit() {
|
||||
draftBio.value = props.profile.bio ?? "";
|
||||
editing.value = "bio";
|
||||
draftBio.value = props.profile.bio ?? ''
|
||||
editing.value = 'bio'
|
||||
}
|
||||
|
||||
function triggerAvatarPicker() {
|
||||
fileInput.value?.click();
|
||||
fileInput.value?.click()
|
||||
}
|
||||
|
||||
async function handleFilePicked(event: Event) {
|
||||
const input = event.target as HTMLInputElement;
|
||||
const file = input.files?.[0];
|
||||
input.value = "";
|
||||
if (!file) return;
|
||||
const input = event.target as HTMLInputElement
|
||||
const file = input.files?.[0]
|
||||
input.value = ''
|
||||
if (!file) return
|
||||
|
||||
if (!/^image\/(png|jpe?g|gif|webp)$/i.test(file.type)) {
|
||||
message.error(t("kol.profile.persona.avatarInvalidType"));
|
||||
return;
|
||||
message.error(t('kol.profile.persona.avatarInvalidType'))
|
||||
return
|
||||
}
|
||||
if (file.size > 2 * 1024 * 1024) {
|
||||
message.error(t("kol.profile.persona.avatarTooLarge"));
|
||||
return;
|
||||
message.error(t('kol.profile.persona.avatarTooLarge'))
|
||||
return
|
||||
}
|
||||
|
||||
avatarUploading.value = true;
|
||||
avatarUploading.value = true
|
||||
try {
|
||||
const uploaded = await kolManageApi.uploadAvatar(file);
|
||||
saveFields({ avatar_url: uploaded.url });
|
||||
const uploaded = await kolManageApi.uploadAvatar(file)
|
||||
saveFields({ avatar_url: uploaded.url })
|
||||
} catch (error) {
|
||||
message.error(formatError(error) || t("kol.profile.persona.saveError"));
|
||||
message.error(formatError(error) || t('kol.profile.persona.saveError'))
|
||||
} finally {
|
||||
avatarUploading.value = false;
|
||||
avatarUploading.value = false
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<section class="persona-card">
|
||||
<div class="persona-avatar-wrap" :class="{ uploading: avatarUploading }" @click="triggerAvatarPicker">
|
||||
<div
|
||||
class="persona-avatar-wrap"
|
||||
:class="{ uploading: avatarUploading }"
|
||||
@click="triggerAvatarPicker"
|
||||
>
|
||||
<template v-if="avatarURL">
|
||||
<img :src="avatarURL" class="persona-avatar" alt="" />
|
||||
</template>
|
||||
@@ -141,7 +149,7 @@ async function handleFilePicked(event: Event) {
|
||||
</template>
|
||||
<div class="persona-avatar-mask">
|
||||
<LoadingOutlined v-if="avatarUploading" />
|
||||
<span v-else>{{ t("kol.profile.persona.avatarLabel") }}</span>
|
||||
<span v-else>{{ t('kol.profile.persona.avatarLabel') }}</span>
|
||||
</div>
|
||||
<input
|
||||
ref="fileInput"
|
||||
@@ -154,7 +162,7 @@ async function handleFilePicked(event: Event) {
|
||||
|
||||
<div class="persona-fields">
|
||||
<div class="persona-field">
|
||||
<label class="persona-field-label">{{ t("kol.profile.persona.displayNameLabel") }}</label>
|
||||
<label class="persona-field-label">{{ t('kol.profile.persona.displayNameLabel') }}</label>
|
||||
<template v-if="editing === 'display_name'">
|
||||
<a-input
|
||||
v-model:value="draftDisplayName"
|
||||
@@ -176,7 +184,7 @@ async function handleFilePicked(event: Event) {
|
||||
</div>
|
||||
|
||||
<div class="persona-field">
|
||||
<label class="persona-field-label">{{ t("kol.profile.persona.bioLabel") }}</label>
|
||||
<label class="persona-field-label">{{ t('kol.profile.persona.bioLabel') }}</label>
|
||||
<template v-if="editing === 'bio'">
|
||||
<a-textarea
|
||||
v-model:value="draftBio"
|
||||
@@ -194,14 +202,14 @@ async function handleFilePicked(event: Event) {
|
||||
<template v-else>
|
||||
<div class="persona-field-static persona-field-static-multiline" @click="startBioEdit">
|
||||
<span class="persona-field-value persona-field-value-bio">
|
||||
{{ profile.bio || t("kol.profile.persona.bioPlaceholder") }}
|
||||
{{ profile.bio || t('kol.profile.persona.bioPlaceholder') }}
|
||||
</span>
|
||||
<EditOutlined class="persona-field-edit-icon" />
|
||||
</div>
|
||||
</template>
|
||||
</div>
|
||||
|
||||
<p class="persona-avatar-hint">{{ t("kol.profile.persona.avatarHint") }}</p>
|
||||
<p class="persona-avatar-hint">{{ t('kol.profile.persona.avatarHint') }}</p>
|
||||
</div>
|
||||
</section>
|
||||
</template>
|
||||
|
||||
Reference in New Issue
Block a user