Update master with new auth (#1236)

* Begin UI for threads and moderation overhaul

* Hide close button on non-report threads

* Fix review age coloring

* Add project count

* Remove action buttons from queue page and add queued date to project page

* Hook up to actual data

* Remove unused icon

* Get up to 1000 projects in queue

* prettier

* more prettier

* Changed all the things

* lint

* rebuild

* Add omorphia

* Workaround formatjs bug in ThreadSummary.vue

* Fix notifications page on prod

* Fix a few notifications and threads bugs

* lockfile

* Fix duplicate button styles

* more fixes and polishing

* More fixes

* Remove legacy pages

* More bugfixes

* Add some error catching for reports and notifications

* More error handling

* fix lint

* Add inbox links

* Remove loading component and rename member header

* Rely on threads always existing

* Handle if project update notifs are not grouped

* oops

* Fix chips on notifications page

* Import ModalModeration

* finish threads

* New authentication (#1234)

* Initial new auth work

* more auth pages

* Finish most

* more

* fix on landing page

* Finish everything but PATs + Sessions

* fix threads merge bugs

* fix cf pages ssr

* fix most issues

* Finish authentication

* Fix merge

---------

Co-authored-by: triphora <emma@modrinth.com>
Co-authored-by: Jai A <jaiagr+gpg@pm.me>
Co-authored-by: Geometrically <18202329+Geometrically@users.noreply.github.com>
This commit is contained in:
Prospector
2023-07-20 11:19:42 -07:00
committed by GitHub
parent a5613ebb10
commit 34d63f3557
72 changed files with 2373 additions and 711 deletions

View File

@@ -1,6 +1,6 @@
<template>
<button class="code" :class="{ copied }" title="Copy code to clipboard" @click="copyText">
{{ text }}
<span>{{ text }}</span>
<CheckIcon v-if="copied" />
<ClipboardCopyIcon v-else />
</button>
@@ -51,6 +51,12 @@ export default {
transition: opacity 0.5s ease-in-out, filter 0.2s ease-in-out, transform 0.05s ease-in-out,
outline 0.2s ease-in-out;
span {
max-width: 10rem;
overflow: hidden;
text-overflow: ellipsis;
}
svg {
width: 1em;
height: 1em;

View File

@@ -7,7 +7,7 @@
v-else-if="
!['resourcepack', 'shader'].includes(type) &&
!(type === 'plugin' && search) &&
!categories.some((x) => $tag.loaderData.dataPackLoaders.includes(x))
!categories.some((x) => tags.loaderData.dataPackLoaders.includes(x))
"
class="environment"
>
@@ -47,57 +47,52 @@
</template>
</span>
</template>
<script>
<script setup>
import InfoIcon from '~/assets/images/utils/info.svg'
import ClientIcon from '~/assets/images/utils/client.svg'
import GlobeIcon from '~/assets/images/utils/globe.svg'
import ServerIcon from '~/assets/images/utils/server.svg'
export default {
components: {
InfoIcon,
ClientIcon,
ServerIcon,
GlobeIcon,
defineProps({
type: {
type: String,
default: 'mod',
},
props: {
type: {
type: String,
default: 'mod',
},
serverSide: {
type: String,
required: false,
default: '',
},
clientSide: {
type: String,
required: false,
default: '',
},
typeOnly: {
type: Boolean,
required: false,
default: false,
},
alwaysShow: {
type: Boolean,
required: false,
default: false,
},
search: {
type: Boolean,
required: false,
default: false,
},
categories: {
type: Array,
required: false,
default() {
return []
},
serverSide: {
type: String,
required: false,
default: '',
},
clientSide: {
type: String,
required: false,
default: '',
},
typeOnly: {
type: Boolean,
required: false,
default: false,
},
alwaysShow: {
type: Boolean,
required: false,
default: false,
},
search: {
type: Boolean,
required: false,
default: false,
},
categories: {
type: Array,
required: false,
default() {
return []
},
},
}
})
const tags = useTags()
</script>
<style lang="scss" scoped>
.environment {

View File

@@ -3,7 +3,7 @@
<div
:class="{
shown: actuallyShown,
noblur: !$orElse($cosmetics.advancedRendering, true),
noblur: !$orElse(cosmetics.advancedRendering, true),
}"
class="modal-overlay"
@click="hide"
@@ -38,6 +38,11 @@ export default {
default: null,
},
},
setup() {
const cosmetics = useCosmetics()
return { cosmetics }
},
data() {
return {
shown: false,

View File

@@ -10,7 +10,7 @@
<Chips
id="project-type"
v-model="projectType"
:items="$tag.projectTypes.map((x) => x.display)"
:items="tags.projectTypes.map((x) => x.display)"
/>
<label for="name">
<span class="label__title">Name<span class="required">*</span></span>
@@ -86,9 +86,14 @@ export default {
default: '',
},
},
setup() {
const tags = useTags()
return { tags }
},
data() {
return {
projectType: this.$tag.projectTypes[0].display,
projectType: this.tags.projectTypes[0].display,
name: '',
slug: '',
description: '',
@@ -100,7 +105,7 @@ export default {
this.$refs.modal.hide()
},
getProjectType() {
return this.$tag.projectTypes.find((x) => this.projectType === x.display)
return this.tags.projectTypes.find((x) => this.projectType === x.display)
},
getClientSide() {
switch (this.getProjectType().id) {
@@ -137,6 +142,8 @@ export default {
const formData = new FormData()
const auth = await useAuth()
formData.append(
'data',
JSON.stringify({
@@ -148,8 +155,8 @@ export default {
initial_versions: [],
team_members: [
{
user_id: this.$auth.user.id,
name: this.$auth.user.username,
user_id: auth.value.user.id,
name: auth.value.user.username,
role: 'Owner',
},
],
@@ -167,7 +174,6 @@ export default {
body: formData,
headers: {
'Content-Disposition': formData,
Authorization: this.$auth.token,
},
})
@@ -193,7 +199,7 @@ export default {
stopLoading()
},
show() {
this.projectType = this.$tag.projectTypes[0].display
this.projectType = this.tags.projectTypes[0].display
this.name = ''
this.slug = ''
this.description = ''

View File

@@ -113,7 +113,6 @@ export default {
await useBaseFetch(`project/${this.project.id}`, {
method: 'PATCH',
body: data,
...this.$defaultHeaders(),
})
this.$refs.modal.hide()

View File

@@ -23,7 +23,7 @@
<Multiselect
id="report-type"
v-model="reportType"
:options="$tag.reportTypes"
:options="tags.reportTypes"
:custom-label="(value) => value.charAt(0).toUpperCase() + value.slice(1)"
:multiple="false"
:searchable="false"
@@ -82,6 +82,11 @@ export default {
default: '',
},
},
setup() {
const tags = useTags()
return { tags }
},
data() {
return {
reportType: '',
@@ -110,7 +115,6 @@ export default {
await useBaseFetch('report', {
method: 'POST',
body: data,
...this.$defaultHeaders(),
})
this.$refs.modal.hide()

View File

@@ -128,14 +128,15 @@ export default {
async proceed() {
startLoading()
try {
await useBaseFetch(`user/${this.$auth.user.id}/payouts`, {
const auth = await useAuth()
await useBaseFetch(`user/${auth.value.user.id}/payouts`, {
method: 'POST',
body: {
amount: Number(this.amount.replace('$', '')),
},
...this.$defaultHeaders(),
})
await useAuth(this.$auth.token)
await useAuth(auth.value.token)
this.$refs.modal.hide()
} catch (err) {

View File

@@ -58,7 +58,7 @@
<nuxt-link :to="getProjectLink(project)" class="title-link">
{{ project.title }}
</nuxt-link>
<template v-if="$tag.rejectedStatuses.includes(notification.body.new_status)">
<template v-if="tags.rejectedStatuses.includes(notification.body.new_status)">
has been <Badge :type="notification.body.new_status" />
</template>
<template v-else>
@@ -106,6 +106,7 @@
:raised="raised"
:messages="getMessages()"
class="thread-summary"
:auth="auth"
/>
<div v-else-if="type === 'project_update'" class="version-list">
<div
@@ -221,7 +222,7 @@
>
<CheckIcon /> Mark as read
</button>
<CopyCode v-if="$cosmetics.developerMode" :text="notification.id" />
<CopyCode v-if="cosmetics.developerMode" :text="notification.id" />
</div>
<div v-else class="input-group">
<nuxt-link
@@ -253,7 +254,7 @@
>
<CheckIcon /> Mark as read
</button>
<CopyCode v-if="$cosmetics.developerMode" :text="notification.id" />
<CopyCode v-if="cosmetics.developerMode" :text="notification.id" />
</div>
</div>
</div>
@@ -301,8 +302,15 @@ const props = defineProps({
type: Boolean,
default: false,
},
auth: {
type: Object,
required: true,
},
})
const cosmetics = useCosmetics()
const tags = useTags()
const type = computed(() =>
!props.notification.body || props.notification.body.type === 'legacy_markdown'
? null
@@ -357,7 +365,6 @@ async function performAction(notification, actionIndex) {
if (actionIndex !== null) {
await useBaseFetch(`${notification.actions[actionIndex].action_route[1]}`, {
method: notification.actions[actionIndex].action_route[0].toUpperCase(),
...app.$defaultHeaders(),
})
}
} catch (err) {

View File

@@ -36,7 +36,7 @@
</p>
<Categories
:categories="
categories.filter((x) => !hideLoaders || !$tag.loaders.find((y) => y.name === x))
categories.filter((x) => !hideLoaders || !tags.loaders.find((y) => y.name === x))
"
:type="type"
class="tags"
@@ -209,6 +209,11 @@ export default {
default: null,
},
},
setup() {
const tags = useTags()
return { tags }
},
computed: {
projectTypeDisplay() {
return this.$getProjectTypeForDisplay(this.type, this.categories)

View File

@@ -1,5 +1,5 @@
<template>
<div v-if="$auth.user && showInvitation" class="universal-card information invited">
<div v-if="showInvitation" class="universal-card information invited">
<h2>Invitation to join project</h2>
<p>
You've been invited be a member of this project with the role of '{{ currentMember.role }}'.
@@ -15,10 +15,9 @@
</div>
<div
v-if="
$auth.user &&
currentMember &&
nags.filter((x) => x.condition).length > 0 &&
(project.status === 'draft' || $tag.rejectedStatuses.includes(project.status))
(project.status === 'draft' || tags.rejectedStatuses.includes(project.status))
"
class="author-actions universal-card"
>
@@ -155,6 +154,14 @@ export default {
type: String,
default: '',
},
auth: {
type: Object,
required: true,
},
tags: {
type: Object,
required: true,
},
setProcessing: {
type: Function,
default() {
@@ -334,7 +341,7 @@ export default {
},
},
{
hide: !this.$tag.rejectedStatuses.includes(this.project.status),
hide: !this.tags.rejectedStatuses.includes(this.project.status),
condition: true,
title: 'Resubmit for review',
id: 'resubmit-for-review',
@@ -363,8 +370,8 @@ export default {
)
},
showInvitation() {
if (this.allMembers && this.$auth) {
const member = this.allMembers.find((x) => x.user.id === this.$auth.user.id)
if (this.allMembers && this.auth) {
const member = this.allMembers.find((x) => x.user.id === this.auth.user.id)
return member && !member.accepted
}
return false

View File

@@ -98,9 +98,10 @@ const props = defineProps({
})
const emit = defineEmits(['switch-page'])
const data = useNuxtApp()
const route = useRoute()
const tags = useTags()
const tempLoaders = new Set()
let tempVersions = new Set()
const tempReleaseChannels = new Set()
@@ -119,7 +120,7 @@ tempVersions = Array.from(tempVersions)
const loaderFilters = shallowRef(Array.from(tempLoaders))
const gameVersionFilters = shallowRef(
data.$tag.gameVersions.filter((gameVer) => tempVersions.includes(gameVer.version))
tags.value.gameVersions.filter((gameVer) => tempVersions.includes(gameVer.version))
)
const versionTypeFilters = shallowRef(Array.from(tempReleaseChannels))
const includeSnapshots = ref(route.query.s === 'true')

View File

@@ -63,10 +63,11 @@
class="thread-summary"
:raised="raised"
:link="`/${moderation ? 'moderation' : 'dashboard'}/report/${report.id}`"
:auth="auth"
/>
<div class="reporter-info">
<ReportIcon class="inline-svg" /> Reported by
<span v-if="$auth.user.id === report.reporterUser.id">you</span>
<span v-if="auth.user.id === report.reporterUser.id">you</span>
<nuxt-link v-else :to="`/user/${report.reporterUser.username}`" class="iconified-link">
<Avatar
:src="report.reporterUser.avatar_url"
@@ -81,7 +82,7 @@
<span v-tooltip="$dayjs(report.created).format('MMMM D, YYYY [at] h:mm A')">{{
fromNow(report.created)
}}</span>
<CopyCode v-if="$cosmetics.developerMode" :text="report.id" class="report-id" />
<CopyCode v-if="cosmetics.developerMode" :text="report.id" class="report-id" />
</div>
</div>
</template>
@@ -117,7 +118,13 @@ defineProps({
type: Boolean,
default: false,
},
auth: {
type: Object,
required: true,
},
})
const cosmetics = useCosmetics()
</script>
<style lang="scss" scoped>

View File

@@ -7,11 +7,16 @@
:link-stack="breadcrumbsStack"
/>
<h2>Report details</h2>
<ReportInfo :report="report" :show-thread="false" :show-message="false" />
<ReportInfo :report="report" :show-thread="false" :show-message="false" :auth="auth" />
</section>
<section class="universal-card">
<h2>Messages</h2>
<ConversationThread :thread="thread" :report="report" :update-thread="updateThread" />
<ConversationThread
:thread="thread"
:report="report"
:update-thread="updateThread"
:auth="auth"
/>
</section>
</div>
</template>
@@ -30,10 +35,12 @@ const props = defineProps({
type: Array,
default: null,
},
auth: {
type: Object,
required: true,
},
})
const app = useNuxtApp()
const report = ref(null)
await fetchReport().then((result) => {
@@ -41,7 +48,7 @@ await fetchReport().then((result) => {
})
const { data: rawThread } = await useAsyncData(`thread/${report.value.thread_id}`, () =>
useBaseFetch(`thread/${report.value.thread_id}`, app.$defaultHeaders())
useBaseFetch(`thread/${report.value.thread_id}`)
)
const thread = computed(() => addReportMessage(rawThread.value, report.value))
@@ -52,7 +59,7 @@ async function updateThread(newThread) {
async function fetchReport() {
const { data: rawReport } = await useAsyncData(`report/${props.reportId}`, () =>
useBaseFetch(`report/${props.reportId}`, app.$defaultHeaders())
useBaseFetch(`report/${props.reportId}`)
)
rawReport.value.item_id = rawReport.value.item_id.replace(/"/g, '')
@@ -67,7 +74,7 @@ async function fetchReport() {
let users = []
if (userIds.length > 0) {
const { data: usersVal } = await useAsyncData(`users?ids=${JSON.stringify(userIds)}`, () =>
useBaseFetch(`users?ids=${JSON.stringify(userIds)}`, app.$defaultHeaders())
useBaseFetch(`users?ids=${encodeURIComponent(JSON.stringify(userIds))}`)
)
users = usersVal.value
}
@@ -75,7 +82,7 @@ async function fetchReport() {
let version = null
if (versionId) {
const { data: versionVal } = await useAsyncData(`version/${versionId}`, () =>
useBaseFetch(`version/${versionId}`, app.$defaultHeaders())
useBaseFetch(`version/${versionId}`)
)
version = versionVal.value
}
@@ -89,7 +96,7 @@ async function fetchReport() {
let project = null
if (projectId) {
const { data: projectVal } = await useAsyncData(`project/${projectId}`, () =>
useBaseFetch(`project/${projectId}`, app.$defaultHeaders())
useBaseFetch(`project/${projectId}`)
)
project = projectVal.value
}

View File

@@ -3,7 +3,7 @@
<ReportInfo
v-for="report in reports.filter(
(x) =>
(moderation || x.reporterUser.id === $auth.user.id) &&
(moderation || x.reporterUser.id === auth.user.id) &&
(viewMode === 'open' ? x.open : !x.open)
)"
:key="report.id"
@@ -11,6 +11,7 @@
:thread="report.thread"
:moderation="moderation"
raised
:auth="auth"
class="universal-card recessed"
/>
</template>
@@ -24,16 +25,16 @@ defineProps({
type: Boolean,
default: false,
},
auth: {
type: Object,
required: true,
},
})
const app = useNuxtApp()
const viewMode = ref('open')
const reports = ref([])
let { data: rawReports } = await useAsyncData('report', () =>
useBaseFetch('report', app.$defaultHeaders())
)
let { data: rawReports } = await useAsyncData('report', () => useBaseFetch('report'))
rawReports = rawReports.value.map((report) => {
report.item_id = report.item_id.replace(/"/g, '')
@@ -53,13 +54,13 @@ const threadIds = [
const [{ data: users }, { data: versions }, { data: threads }] = await Promise.all([
await useAsyncData(`users?ids=${JSON.stringify(userIds)}`, () =>
useBaseFetch(`users?ids=${JSON.stringify(userIds)}`, app.$defaultHeaders())
useBaseFetch(`users?ids=${encodeURIComponent(JSON.stringify(userIds))}`)
),
await useAsyncData(`versions?ids=${JSON.stringify(versionIds)}`, () =>
useBaseFetch(`versions?ids=${JSON.stringify(versionIds)}`, app.$defaultHeaders())
useBaseFetch(`versions?ids=${encodeURIComponent(JSON.stringify(versionIds))}`)
),
await useAsyncData(`threads?ids=${JSON.stringify(threadIds)}`, () =>
useBaseFetch(`threads?ids=${JSON.stringify(threadIds)}`, app.$defaultHeaders())
useBaseFetch(`threads?ids=${encodeURIComponent(JSON.stringify(threadIds))}`)
),
])
@@ -70,7 +71,7 @@ const versionProjects = versions.value.map((version) => version.project_id)
const projectIds = [...new Set(reportedProjects.concat(versionProjects))]
const { data: projects } = await useAsyncData(`projects?ids=${JSON.stringify(projectIds)}`, () =>
useBaseFetch(`projects?ids=${JSON.stringify(projectIds)}`, app.$defaultHeaders())
useBaseFetch(`projects?ids=${encodeURIComponent(JSON.stringify(projectIds))}`)
)
reports.value = rawReports.map((report) => {

View File

@@ -23,10 +23,15 @@ export default {
required: true,
},
},
setup() {
const tags = useTags()
return { tags }
},
computed: {
categoriesFiltered() {
return this.$tag.categories
.concat(this.$tag.loaders)
return this.tags.categories
.concat(this.tags.loaders)
.filter(
(x) =>
this.categories.includes(x.name) && (!x.project_type || x.project_type === this.type)

View File

@@ -33,7 +33,7 @@
</div>
</div>
</Modal>
<div v-if="$cosmetics.developerMode" class="thread-id">
<div v-if="cosmetics.developerMode" class="thread-id">
Thread ID: <CopyCode :text="thread.id" />
</div>
<div v-if="sortedMessages.length > 0" class="messages universal-card recessed">
@@ -77,7 +77,7 @@
>
<SendIcon /> Send
</button>
<template v-if="currentMember && !isStaff($auth.user)">
<template v-if="currentMember && !isStaff(auth.user)">
<template v-if="isRejected(project)">
<button
v-if="replyBody"
@@ -98,7 +98,7 @@
<div class="spacer"></div>
<div class="input-group extra-options">
<template v-if="report">
<template v-if="isStaff($auth.user)">
<template v-if="isStaff(auth.user)">
<button
v-if="replyBody"
class="iconified-button danger-button"
@@ -112,7 +112,7 @@
</template>
</template>
<template v-if="project">
<template v-if="isStaff($auth.user)">
<template v-if="isStaff(auth.user)">
<button
v-if="replyBody"
class="iconified-button brand-button"
@@ -216,8 +216,13 @@ const props = defineProps({
return null
},
},
auth: {
type: Object,
required: true,
},
})
const app = useNuxtApp()
const cosmetics = useCosmetics()
const members = computed(() => {
const members = {}
@@ -250,7 +255,7 @@ async function updateThreadLocal() {
}
let thread = null
if (threadId) {
thread = await useBaseFetch(`thread/${threadId}`, app.$defaultHeaders())
thread = await useBaseFetch(`thread/${threadId}`)
}
props.updateThread(thread)
}
@@ -265,7 +270,6 @@ async function sendReply(status = null) {
body: replyBody.value,
},
},
...app.$defaultHeaders(),
})
replyBody.value = ''
await updateThreadLocal()
@@ -293,7 +297,6 @@ async function closeReport(reply) {
body: {
closed: true,
},
...app.$defaultHeaders(),
})
await updateThreadLocal()
} catch (err) {

View File

@@ -51,6 +51,10 @@ const props = defineProps({
return []
},
},
auth: {
type: Object,
required: true,
},
})
const app = useNuxtApp()
@@ -60,7 +64,7 @@ const members = computed(() => {
for (const member of props.thread.members) {
members[member.id] = member
}
members[app.$auth.user.id] = app.$auth.user
members[props.auth.user.id] = props.auth.user
return members
})