Survey notices for Servers (#3514)
* Survey notices for Servers * lint * remove creepy frog
This commit is contained in:
parent
76be502e16
commit
6aa6db4e8c
@ -6,29 +6,6 @@
|
|||||||
}}</span>
|
}}</span>
|
||||||
</template>
|
</template>
|
||||||
<div class="flex w-[700px] flex-col gap-3">
|
<div class="flex w-[700px] flex-col gap-3">
|
||||||
<div class="flex flex-col gap-2">
|
|
||||||
<label for="notice-title" class="flex flex-col gap-1">
|
|
||||||
<span class="text-lg font-semibold text-contrast"> Title </span>
|
|
||||||
</label>
|
|
||||||
<input
|
|
||||||
id="notice-title"
|
|
||||||
v-model="newNoticeTitle"
|
|
||||||
placeholder="E.g. Maintenance"
|
|
||||||
type="text"
|
|
||||||
autocomplete="off"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
<div class="flex flex-col gap-2">
|
|
||||||
<label for="notice-message" class="flex flex-col gap-1">
|
|
||||||
<span class="text-lg font-semibold text-contrast">
|
|
||||||
Message
|
|
||||||
<span class="text-brand-red">*</span>
|
|
||||||
</span>
|
|
||||||
</label>
|
|
||||||
<div class="textarea-wrapper h-32">
|
|
||||||
<textarea id="notice-message" v-model="newNoticeMessage" />
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="flex items-center justify-between gap-2">
|
<div class="flex items-center justify-between gap-2">
|
||||||
<label for="level-selector" class="flex flex-col gap-1">
|
<label for="level-selector" class="flex flex-col gap-1">
|
||||||
<span class="text-lg font-semibold text-contrast"> Level </span>
|
<span class="text-lg font-semibold text-contrast"> Level </span>
|
||||||
@ -43,7 +20,38 @@
|
|||||||
name="Level"
|
name="Level"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div class="flex items-center justify-between gap-2">
|
<div v-if="!newNoticeSurvey" class="flex flex-col gap-2">
|
||||||
|
<label for="notice-title" class="flex flex-col gap-1">
|
||||||
|
<span class="text-lg font-semibold text-contrast"> Title </span>
|
||||||
|
</label>
|
||||||
|
<input
|
||||||
|
id="notice-title"
|
||||||
|
v-model="newNoticeTitle"
|
||||||
|
placeholder="E.g. Maintenance"
|
||||||
|
type="text"
|
||||||
|
autocomplete="off"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div class="flex flex-col gap-2">
|
||||||
|
<label for="notice-message" class="flex flex-col gap-1">
|
||||||
|
<span class="text-lg font-semibold text-contrast">
|
||||||
|
{{ newNoticeSurvey ? "Survey ID" : "Message" }}
|
||||||
|
<span class="text-brand-red">*</span>
|
||||||
|
</span>
|
||||||
|
</label>
|
||||||
|
<input
|
||||||
|
v-if="newNoticeSurvey"
|
||||||
|
id="notice-message"
|
||||||
|
v-model="newNoticeMessage"
|
||||||
|
placeholder="E.g. rXGtq2"
|
||||||
|
type="text"
|
||||||
|
autocomplete="off"
|
||||||
|
/>
|
||||||
|
<div v-else class="textarea-wrapper h-32">
|
||||||
|
<textarea id="notice-message" v-model="newNoticeMessage" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div v-if="!newNoticeSurvey" class="flex items-center justify-between gap-2">
|
||||||
<label for="dismissable-toggle" class="flex flex-col gap-1">
|
<label for="dismissable-toggle" class="flex flex-col gap-1">
|
||||||
<span class="text-lg font-semibold text-contrast"> Dismissable </span>
|
<span class="text-lg font-semibold text-contrast"> Dismissable </span>
|
||||||
<span>Allow users to dismiss the notice from their panel.</span>
|
<span>Allow users to dismiss the notice from their panel.</span>
|
||||||
@ -75,7 +83,7 @@
|
|||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="flex flex-col gap-2">
|
<div v-if="!newNoticeSurvey" class="flex flex-col gap-2">
|
||||||
<span class="text-lg font-semibold text-contrast"> Preview </span>
|
<span class="text-lg font-semibold text-contrast"> Preview </span>
|
||||||
<ServerNotice
|
<ServerNotice
|
||||||
:level="newNoticeLevel.id"
|
:level="newNoticeLevel.id"
|
||||||
@ -355,6 +363,7 @@ async function deleteNotice(notice: ServerNoticeType) {
|
|||||||
|
|
||||||
const trimmedMessage = computed(() => newNoticeMessage.value?.trim());
|
const trimmedMessage = computed(() => newNoticeMessage.value?.trim());
|
||||||
const trimmedTitle = computed(() => newNoticeTitle.value?.trim());
|
const trimmedTitle = computed(() => newNoticeTitle.value?.trim());
|
||||||
|
const newNoticeSurvey = computed(() => newNoticeLevel.value.id === "survey");
|
||||||
|
|
||||||
const noticeSubmitError = computed(() => {
|
const noticeSubmitError = computed(() => {
|
||||||
let error: undefined | string;
|
let error: undefined | string;
|
||||||
@ -389,9 +398,9 @@ async function saveChanges() {
|
|||||||
method: "PATCH",
|
method: "PATCH",
|
||||||
body: {
|
body: {
|
||||||
message: newNoticeMessage.value,
|
message: newNoticeMessage.value,
|
||||||
title: trimmedTitle.value,
|
title: newNoticeSurvey.value ? undefined : trimmedTitle.value,
|
||||||
level: newNoticeLevel.value.id,
|
level: newNoticeLevel.value.id,
|
||||||
dismissable: newNoticeDismissable.value,
|
dismissable: newNoticeSurvey.value ? true : newNoticeDismissable.value,
|
||||||
announce_at: newNoticeScheduledDate.value
|
announce_at: newNoticeScheduledDate.value
|
||||||
? dayjs(newNoticeScheduledDate.value).toISOString()
|
? dayjs(newNoticeScheduledDate.value).toISOString()
|
||||||
: dayjs().toISOString(),
|
: dayjs().toISOString(),
|
||||||
@ -420,9 +429,9 @@ async function createNotice() {
|
|||||||
method: "POST",
|
method: "POST",
|
||||||
body: {
|
body: {
|
||||||
message: newNoticeMessage.value,
|
message: newNoticeMessage.value,
|
||||||
title: trimmedTitle.value,
|
title: newNoticeSurvey.value ? undefined : trimmedTitle.value,
|
||||||
level: newNoticeLevel.value.id,
|
level: newNoticeLevel.value.id,
|
||||||
dismissable: newNoticeDismissable.value,
|
dismissable: newNoticeSurvey.value ? true : newNoticeDismissable.value,
|
||||||
announce_at: newNoticeScheduledDate.value
|
announce_at: newNoticeScheduledDate.value
|
||||||
? dayjs(newNoticeScheduledDate.value).toISOString()
|
? dayjs(newNoticeScheduledDate.value).toISOString()
|
||||||
: dayjs().toISOString(),
|
: dayjs().toISOString(),
|
||||||
|
|||||||
@ -1,11 +1,11 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="contents">
|
<div class="contents">
|
||||||
<div
|
<div
|
||||||
v-if="serverData?.notices && serverData.notices.length > 0"
|
v-if="filteredNotices.length > 0"
|
||||||
class="experimental-styles-within relative mx-auto flex w-full min-w-0 max-w-[1280px] flex-col gap-3 px-6"
|
class="experimental-styles-within relative mx-auto flex w-full min-w-0 max-w-[1280px] flex-col gap-3 px-6"
|
||||||
>
|
>
|
||||||
<ServerNotice
|
<ServerNotice
|
||||||
v-for="notice in serverData?.notices"
|
v-for="notice in filteredNotices"
|
||||||
:key="`notice-${notice.id}`"
|
:key="`notice-${notice.id}`"
|
||||||
:level="notice.level"
|
:level="notice.level"
|
||||||
:message="notice.message"
|
:message="notice.message"
|
||||||
@ -430,15 +430,13 @@ const isFirstMount = ref(true);
|
|||||||
const isMounted = ref(true);
|
const isMounted = ref(true);
|
||||||
|
|
||||||
const INTERCOM_APP_ID = ref("ykeritl9");
|
const INTERCOM_APP_ID = ref("ykeritl9");
|
||||||
const auth = await useAuth();
|
const auth = (await useAuth()) as unknown as {
|
||||||
// @ts-expect-error - Auth is untyped
|
value: { user: { id: string; username: string; email: string; created: string } };
|
||||||
|
};
|
||||||
const userId = ref(auth.value?.user?.id ?? null);
|
const userId = ref(auth.value?.user?.id ?? null);
|
||||||
// @ts-expect-error - Auth is untyped
|
|
||||||
const username = ref(auth.value?.user?.username ?? null);
|
const username = ref(auth.value?.user?.username ?? null);
|
||||||
// @ts-expect-error - Auth is untyped
|
|
||||||
const email = ref(auth.value?.user?.email ?? null);
|
const email = ref(auth.value?.user?.email ?? null);
|
||||||
const createdAt = ref(
|
const createdAt = ref(
|
||||||
// @ts-expect-error - Auth is untyped
|
|
||||||
auth.value?.user?.created ? Math.floor(new Date(auth.value.user.created).getTime() / 1000) : null,
|
auth.value?.user?.created ? Math.floor(new Date(auth.value.user.created).getTime() / 1000) : null,
|
||||||
);
|
);
|
||||||
|
|
||||||
@ -544,6 +542,99 @@ const navLinks = [
|
|||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
|
const filteredNotices = computed(
|
||||||
|
() => serverData.value?.notices?.filter((n) => n.level !== "survey") ?? [],
|
||||||
|
);
|
||||||
|
const surveyNotice = computed(() => serverData.value?.notices?.find((n) => n.level === "survey"));
|
||||||
|
|
||||||
|
async function dismissSurvey() {
|
||||||
|
const noticeId = surveyNotice.value?.id;
|
||||||
|
if (noticeId === undefined) {
|
||||||
|
console.warn("No survey notice to dismiss");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
await dismissNotice(noticeId);
|
||||||
|
console.log(`Dismissed survey notice ${noticeId}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
type TallyPopupOptions = {
|
||||||
|
key?: string;
|
||||||
|
layout?: "default" | "modal";
|
||||||
|
width?: number;
|
||||||
|
alignLeft?: boolean;
|
||||||
|
hideTitle?: boolean;
|
||||||
|
overlay?: boolean;
|
||||||
|
emoji?: {
|
||||||
|
text: string;
|
||||||
|
animation:
|
||||||
|
| "none"
|
||||||
|
| "wave"
|
||||||
|
| "tada"
|
||||||
|
| "heart-beat"
|
||||||
|
| "spin"
|
||||||
|
| "flash"
|
||||||
|
| "bounce"
|
||||||
|
| "rubber-band"
|
||||||
|
| "head-shake";
|
||||||
|
};
|
||||||
|
autoClose?: number;
|
||||||
|
showOnce?: boolean;
|
||||||
|
doNotShowAfterSubmit?: boolean;
|
||||||
|
customFormUrl?: string;
|
||||||
|
hiddenFields?: {
|
||||||
|
[key: string]: unknown;
|
||||||
|
};
|
||||||
|
onOpen?: () => void;
|
||||||
|
onClose?: () => void;
|
||||||
|
onPageView?: (page: number) => void;
|
||||||
|
onSubmit?: (payload: unknown) => void;
|
||||||
|
};
|
||||||
|
|
||||||
|
const popupOptions = computed(
|
||||||
|
() =>
|
||||||
|
({
|
||||||
|
layout: "default",
|
||||||
|
width: 400,
|
||||||
|
autoClose: 2000,
|
||||||
|
hideTitle: true,
|
||||||
|
hiddenFields: {
|
||||||
|
username: auth.value?.user?.username,
|
||||||
|
user_id: auth.value?.user?.id,
|
||||||
|
user_email: auth.value?.user?.email,
|
||||||
|
server_id: serverData.value?.server_id,
|
||||||
|
loader: serverData.value?.loader,
|
||||||
|
game_version: serverData.value?.mc_version,
|
||||||
|
modpack_id: serverData.value?.project?.id,
|
||||||
|
modpack_name: serverData.value?.project?.title,
|
||||||
|
},
|
||||||
|
onOpen: () => console.log(`Opened survey notice: ${surveyNotice.value?.id}`),
|
||||||
|
onClose: async () => await dismissSurvey(),
|
||||||
|
onSubmit: (payload: any) => {
|
||||||
|
console.log("Form submitted:", payload);
|
||||||
|
},
|
||||||
|
}) satisfies TallyPopupOptions,
|
||||||
|
);
|
||||||
|
|
||||||
|
function showSurvey() {
|
||||||
|
if (!surveyNotice.value) {
|
||||||
|
console.warn("No survey notice to open");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
if ((window as any).Tally?.openPopup) {
|
||||||
|
console.log(
|
||||||
|
`Opening Tally popup for survey notice ${surveyNotice.value?.id} (form ID: ${surveyNotice.value?.message})`,
|
||||||
|
);
|
||||||
|
(window as any).Tally.openPopup(surveyNotice.value?.message, popupOptions.value);
|
||||||
|
} else {
|
||||||
|
console.warn("Tally script not yet loaded");
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
console.error("Error opening Tally popup:", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
const connectWebSocket = () => {
|
const connectWebSocket = () => {
|
||||||
if (!isMounted.value) return;
|
if (!isMounted.value) return;
|
||||||
|
|
||||||
@ -1006,6 +1097,10 @@ onMounted(() => {
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|
||||||
|
if (surveyNotice.value) {
|
||||||
|
showSurvey();
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
onUnmounted(() => {
|
onUnmounted(() => {
|
||||||
@ -1030,6 +1125,15 @@ watch(
|
|||||||
definePageMeta({
|
definePageMeta({
|
||||||
middleware: "auth",
|
middleware: "auth",
|
||||||
});
|
});
|
||||||
|
|
||||||
|
useHead({
|
||||||
|
script: [
|
||||||
|
{
|
||||||
|
src: "https://tally.so/widgets/embed.js",
|
||||||
|
defer: true,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style>
|
<style>
|
||||||
|
|||||||
@ -1,5 +1,11 @@
|
|||||||
<template>
|
<template>
|
||||||
<Admonition :type="NOTICE_TYPE[props.level]">
|
<div
|
||||||
|
v-if="level === 'survey'"
|
||||||
|
class="flex items-center gap-2 border-2 border-solid border-brand-purple bg-bg-purple p-4 rounded-2xl"
|
||||||
|
>
|
||||||
|
<span class="text-contrast font-bold">Survey ID:</span> <CopyCode :text="message" />
|
||||||
|
</div>
|
||||||
|
<Admonition v-else :type="NOTICE_TYPE[level]">
|
||||||
<template #header>
|
<template #header>
|
||||||
<template v-if="!hideDefaultTitle">
|
<template v-if="!hideDefaultTitle">
|
||||||
{{ formatMessage(heading) }}
|
{{ formatMessage(heading) }}
|
||||||
@ -32,6 +38,7 @@ import { XIcon } from '@modrinth/assets'
|
|||||||
import { defineMessages, type MessageDescriptor, useVIntl } from '@vintl/vintl'
|
import { defineMessages, type MessageDescriptor, useVIntl } from '@vintl/vintl'
|
||||||
import { computed } from 'vue'
|
import { computed } from 'vue'
|
||||||
import ButtonStyled from './ButtonStyled.vue'
|
import ButtonStyled from './ButtonStyled.vue'
|
||||||
|
import CopyCode from './CopyCode.vue'
|
||||||
|
|
||||||
const { formatMessage } = useVIntl()
|
const { formatMessage } = useVIntl()
|
||||||
const emit = defineEmits<{
|
const emit = defineEmits<{
|
||||||
|
|||||||
@ -34,6 +34,16 @@ export const NOTICE_LEVELS: Record<
|
|||||||
bg: 'var(--color-red-bg)',
|
bg: 'var(--color-red-bg)',
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
survey: {
|
||||||
|
name: defineMessage({
|
||||||
|
id: 'servers.notice.level.survey.name',
|
||||||
|
defaultMessage: 'Survey',
|
||||||
|
}),
|
||||||
|
colors: {
|
||||||
|
text: 'var(--color-purple)',
|
||||||
|
bg: 'var(--color-purple-bg)',
|
||||||
|
},
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
const DISMISSABLE = {
|
const DISMISSABLE = {
|
||||||
|
|||||||
@ -257,7 +257,7 @@ export type ServerNotice = {
|
|||||||
id: number
|
id: number
|
||||||
message: string
|
message: string
|
||||||
title?: string
|
title?: string
|
||||||
level: string
|
level: 'info' | 'warn' | 'critical' | 'survey'
|
||||||
dismissable: boolean
|
dismissable: boolean
|
||||||
announce_at: string
|
announce_at: string
|
||||||
expires: string
|
expires: string
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user