1
0
Files
AstralRinth/apps/frontend/src/pages/settings/authorizations.vue
Evan Song abec2e48d4 Add TailwindCSS (#1252)
* Setup TailwindCSS

* Fully setup configuration

* Refactor some tailwind variables
2024-07-06 20:57:32 -07:00

246 lines
5.9 KiB
Vue

<template>
<div class="universal-card">
<ConfirmModal
ref="modal_confirm"
title="Are you sure you want to revoke this application?"
description="This will revoke the application's access to your account. You can always re-authorize it later."
proceed-label="Revoke"
@proceed="revokeApp(revokingId)"
/>
<h2>{{ formatMessage(commonSettingsMessages.authorizedApps) }}</h2>
<p>
When you authorize an application with your Modrinth account, you grant it access to your
account. You can manage and review access to your account here at any time.
</p>
<div v-if="appInfoLookup.length === 0" class="universal-card recessed">
You have not authorized any applications.
</div>
<div
v-for="authorization in appInfoLookup"
:key="authorization.id"
class="universal-card recessed token"
>
<div class="token-content">
<div>
<div class="icon-name">
<Avatar :src="authorization.app.icon_url" />
<div>
<h2 class="token-title">
{{ authorization.app.name }}
</h2>
<div>
by
<nuxt-link class="text-link" :to="'/user/' + authorization.owner.id">{{
authorization.owner.username
}}</nuxt-link>
<template v-if="authorization.app.url">
<span> </span>
<nuxt-link class="text-link" :to="authorization.app.url">
{{ authorization.app.url }}
</nuxt-link>
</template>
</div>
</div>
</div>
</div>
<div>
<template v-if="authorization.app.description">
<label for="app-description">
<span class="label__title"> About this app </span>
</label>
<div id="app-description">{{ authorization.app.description }}</div>
</template>
<label for="app-scope-list">
<span class="label__title">Scopes</span>
</label>
<div class="scope-list">
<div
v-for="scope in scopesToDefinitions(authorization.scopes)"
:key="scope"
class="scope-list-item"
>
<div class="scope-list-item-icon">
<CheckIcon />
</div>
{{ scope }}
</div>
</div>
</div>
</div>
<div class="input-group">
<Button
color="danger"
icon-only
@click="
() => {
revokingId = authorization.app_id;
$refs.modal_confirm.show();
}
"
>
<TrashIcon />
Revoke
</Button>
</div>
</div>
</div>
</template>
<script setup>
import { Button, ConfirmModal, Avatar } from "@modrinth/ui";
import { TrashIcon, CheckIcon } from "@modrinth/assets";
import { commonSettingsMessages } from "~/utils/common-messages.ts";
import { useScopes } from "~/composables/auth/scopes.ts";
const { formatMessage } = useVIntl();
const { scopesToDefinitions } = useScopes();
const revokingId = ref(null);
definePageMeta({
middleware: "auth",
});
useHead({
title: "Authorizations - Modrinth",
});
const { data: usersApps, refresh } = await useAsyncData("userAuthorizations", () =>
useBaseFetch(`oauth/authorizations`, {
internal: true,
}),
);
const { data: appInformation } = await useAsyncData(
"appInfo",
() =>
useBaseFetch("oauth/apps", {
internal: true,
query: {
ids: usersApps.value.map((c) => c.app_id).join(","),
},
}),
{
watch: usersApps,
},
);
const { data: appCreatorsInformation } = await useAsyncData(
"appCreatorsInfo",
() =>
useBaseFetch("users", {
query: {
ids: JSON.stringify(appInformation.value.map((c) => c.created_by)),
},
}),
{
watch: appInformation,
},
);
const appInfoLookup = computed(() => {
return usersApps.value.map((app) => {
const info = appInformation.value.find((c) => c.id === app.app_id);
const owner = appCreatorsInformation.value.find((c) => c.id === info.created_by);
return {
...app,
app: info || null,
owner: owner || null,
};
});
});
async function revokeApp(id) {
try {
await useBaseFetch(`oauth/authorizations`, {
internal: true,
method: "DELETE",
query: {
client_id: id,
},
});
revokingId.value = null;
await refresh();
} catch (err) {
data.$notify({
group: "main",
title: "An error occurred",
text: err.data ? err.data.description : err,
type: "error",
});
}
}
</script>
<style lang="scss" scoped>
.input-group {
// Overrides for omorphia compat
> * {
padding: var(--gap-sm) var(--gap-lg) !important;
}
}
.scope-list {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(16rem, 1fr));
gap: var(--gap-sm);
.scope-list-item {
display: flex;
align-items: center;
gap: 0.5rem;
border-radius: 0.25rem;
background-color: var(--color-gray-200);
color: var(--color-gray-700);
font-size: 0.875rem;
font-weight: 500;
line-height: 1.25rem;
// avoid breaking text or overflowing
white-space: nowrap;
overflow: hidden;
}
.scope-list-item-icon {
width: 1.25rem;
height: 1.25rem;
flex: 0 0 auto;
border-radius: 50%;
display: flex;
align-items: center;
justify-content: center;
background-color: var(--color-green);
color: var(--color-raised-bg);
}
}
.icon-name {
display: flex;
align-items: flex-start;
gap: var(--gap-lg);
padding-bottom: var(--gap-sm);
}
.token-content {
width: 100%;
.token-title {
margin-bottom: var(--spacing-card-xs);
}
}
.token {
display: flex;
flex-direction: column;
gap: 0.5rem;
@media screen and (min-width: 800px) {
flex-direction: row;
align-items: flex-start;
}
}
</style>