Skip to content

Commit ecd5891

Browse files
committed
[common] feat: user center
1 parent 9554ca8 commit ecd5891

58 files changed

Lines changed: 1053 additions & 152 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

apps/app/src/components/WindowButtons.vue

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
<script setup lang="ts">
2-
import Button from "@repo/ui/src/components/form/button/Button.vue";
2+
import Button from "@repo/ui/src/components/button/Button.vue";
33
import { platform } from "@tauri-apps/plugin-os";
44
import { exit } from "@tauri-apps/plugin-process";
55
import { getCurrentWindow } from "@tauri-apps/api/window";

apps/web/package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@
2020
"@repo/ui": "workspace:*",
2121
"@vueuse/core": "^14.2.1",
2222
"axios": "^1.13.5",
23+
"dayjs": "^1.11.19",
2324
"eslint": "^10.0.0",
2425
"pinia": "^3.0.4",
2526
"vue": "^3.5.28",

apps/web/src-frontend/App.vue

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,12 @@
11
<script setup lang="ts">
22
import SharedApp from "@repo/shared/src/App.vue";
33
import TaskExitDialog from "./components/TaskExitDialog.vue";
4+
import UserCenterModal from "./components/userCenter/UserCenterModal.vue";
45
</script>
56

67
<template>
78
<SharedApp />
9+
<UserCenterModal />
810
<TaskExitDialog />
911
</template>
1012

apps/web/src-frontend/components/TaskExitDialog.vue

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ import { useI18n } from "vue-i18n";
44
import { disableTaskExitDialog, showTaskExitDialog } from "../index.ts";
55
import FormItem from "@repo/ui/src/components/form/FormItem.vue";
66
import Checkbox from "@repo/ui/src/components/form/entries/Checkbox.vue";
7-
import Button from "@repo/ui/src/components/form/button/Button.vue";
7+
import Button from "@repo/ui/src/components/button/Button.vue";
88
import * as yup from "yup";
99
1010
const t = useI18n().t;
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
<script setup lang="ts">
2+
import { useAccount } from "../../utils/store.ts";
3+
</script>
4+
5+
<template>
6+
<ul class="permissions">
7+
<li v-for="perm in useAccount().selfInfo.permissions" :key="perm">
8+
<h4>{{ perm.replaceAll("*", "*") }}</h4>
9+
</li>
10+
</ul>
11+
</template>
12+
13+
<style scoped lang="scss">
14+
.permissions {
15+
flex: 1;
16+
height: 20rem;
17+
display: flex;
18+
flex-direction: column;
19+
gap: var(--mcsl-spacing-2xs);
20+
overflow: auto;
21+
background: var(--mcsl-bg-color-main);
22+
margin: 0;
23+
padding: var(--mcsl-spacing-xs);
24+
padding-left: var(--mcsl-spacing-lg);
25+
border-radius: var(--mcsl-border-radius-md);
26+
27+
& h4 {
28+
font-feature-settings: "ss08";
29+
}
30+
}
31+
</style>
Lines changed: 134 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,134 @@
1+
<script setup lang="ts">
2+
import { useI18n } from "vue-i18n";
3+
import Spinner from "@repo/ui/src/components/progress/Spinner.vue";
4+
import { ref } from "vue";
5+
import type { SessionInfo } from "../../utils/types.ts";
6+
import { notifyErr, requestWithToken } from "../../utils/network.ts";
7+
import Tag from "@repo/ui/src/components/misc/tag/Tag.vue";
8+
import Button from "@repo/ui/src/components/button/Button.vue";
9+
import { formatDate } from "@repo/ui/src/utils/utils.ts";
10+
import dayjs from "dayjs";
11+
import { MCSLNotif } from "@repo/ui/src/utils/notifications.ts";
12+
import ConfirmModal from "@repo/shared/src/components/ConfirmModal.vue";
13+
14+
const t = useI18n().t;
15+
16+
const sessions = ref<SessionInfo[]>();
17+
let sessionToDelete = "";
18+
const showDeleteConfirm = ref(false);
19+
20+
async function refresh() {
21+
sessions.value = await requestWithToken<SessionInfo[]>(
22+
"/session/self",
23+
"GET",
24+
(e) => notifyErr(e, "web.user-center.sessions.error"),
25+
);
26+
}
27+
28+
async function deleteSession() {
29+
await requestWithToken<void>("/session/" + sessionToDelete, "DELETE", (e) =>
30+
notifyErr(e, "web.user-center.sessions.delete.error"),
31+
);
32+
33+
new MCSLNotif({
34+
data: {
35+
color: "success",
36+
title: t("ui.notification.title.success"),
37+
message: t("web.user-center.sessions.delete.success"),
38+
},
39+
}).open();
40+
41+
await refresh();
42+
}
43+
44+
refresh();
45+
</script>
46+
47+
<template>
48+
<div class="sessions">
49+
<ConfirmModal
50+
v-model:visible="showDeleteConfirm"
51+
:title="t('web.user-center.sessions.delete.confirm')"
52+
@confirm="deleteSession"
53+
/>
54+
<table v-if="sessions" class="sessions__table">
55+
<thead>
56+
<tr>
57+
<th>{{ t("web.user-center.sessions.info.type.label") }}</th>
58+
<th>{{ t("web.user-center.sessions.info.user-agent") }}</th>
59+
<th>{{ t("web.user-center.sessions.info.created-at") }}</th>
60+
<th>{{ t("web.user-center.sessions.info.last-active-ip") }}</th>
61+
<th>{{ t("web.user-center.sessions.info.last-active-at") }}</th>
62+
<th>{{ t("ui.common.actions") }}</th>
63+
</tr>
64+
</thead>
65+
<tbody>
66+
<tr v-for="session in sessions" :key="session.token_id">
67+
<td>
68+
<Tag :color="session.remember ? 'primary' : 'default'">
69+
{{
70+
t(
71+
"web.user-center.sessions.info.type." +
72+
(session.remember ? "remember" : "temporary"),
73+
)
74+
}}
75+
</Tag>
76+
</td>
77+
<td v-tooltip="session.user_agent" class="sessions__text-eclipse">
78+
{{ session.user_agent }}
79+
</td>
80+
<td>
81+
{{ formatDate(dayjs(session.created_at)) }}
82+
</td>
83+
<td>
84+
{{ session.last_active_ip }}
85+
</td>
86+
<td>
87+
{{ formatDate(dayjs(session.last_active_at)) }}
88+
</td>
89+
<td>
90+
<Button
91+
icon="fa-solid fa-trash-can"
92+
color="danger"
93+
rounded
94+
size="small"
95+
v-tooltip="t('ui.common.delete')"
96+
@click="
97+
() => {
98+
sessionToDelete = session.token_id;
99+
showDeleteConfirm = true;
100+
}
101+
"
102+
/>
103+
</td>
104+
</tr>
105+
</tbody>
106+
</table>
107+
<Spinner v-else class="sessions__spinner" />
108+
</div>
109+
</template>
110+
111+
<style scoped lang="scss">
112+
.sessions {
113+
overflow: auto;
114+
}
115+
116+
.sessions__table {
117+
min-width: 540px;
118+
}
119+
120+
.sessions__text-eclipse {
121+
max-width: 80px;
122+
overflow: hidden;
123+
text-overflow: ellipsis;
124+
white-space: nowrap;
125+
cursor: help;
126+
}
127+
128+
.sessions__spinner {
129+
height: 6rem;
130+
display: flex;
131+
justify-content: center;
132+
align-items: center;
133+
}
134+
</style>
Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
1+
<script setup lang="ts">
2+
import { showUserCenterModal, userCenterPage } from "../../index.ts";
3+
import Modal from "@repo/ui/src/components/overlay/Modal.vue";
4+
import Sidebar from "@repo/ui/src/components/navigation/Sidebar.vue";
5+
import { useAccount, useWebNavigation } from "../../utils/store.ts";
6+
import { useI18n } from "vue-i18n";
7+
8+
const t = useI18n().t;
9+
const sidebar = useWebNavigation().get("userCenter");
10+
11+
useAccount().updateSelfInfo();
12+
13+
sidebar.value[0]!.onClick!();
14+
</script>
15+
16+
<template>
17+
<Modal
18+
v-model:visible="showUserCenterModal"
19+
:header="t('web.user-center.title')"
20+
max-width="800px"
21+
@close="sidebar[0]!.onClick"
22+
>
23+
<div class="user-center__body">
24+
<Sidebar :pages="sidebar" />
25+
<transition
26+
v-if="userCenterPage"
27+
mode="out-in"
28+
name="stretch-vertical"
29+
:duration="500"
30+
>
31+
<component :is="userCenterPage[1]" />
32+
</transition>
33+
</div>
34+
</Modal>
35+
</template>
36+
37+
<style scoped lang="scss">
38+
.user-center__body {
39+
display: flex;
40+
gap: var(--mcsl-spacing-sm);
41+
42+
@media (max-width: 768px) {
43+
flex-direction: column;
44+
}
45+
46+
& > div:first-child {
47+
width: 13rem;
48+
padding-right: var(--mcsl-spacing-sm);
49+
border-right: 1px solid var(--mcsl-border-color-base);
50+
51+
@media (max-width: 768px) {
52+
width: 100%;
53+
padding-right: 0;
54+
border-right: none;
55+
padding-bottom: var(--mcsl-spacing-2xs);
56+
border-bottom: 1px solid var(--mcsl-border-color-base);
57+
}
58+
}
59+
60+
& > div:last-child {
61+
width: 0;
62+
flex: 1;
63+
64+
@media (max-width: 768px) {
65+
width: 100%;
66+
}
67+
}
68+
}
69+
</style>

0 commit comments

Comments
 (0)