This commit is contained in:
ClasWen 2024-08-24 16:55:33 +08:00
parent aa39717a07
commit a29e54b0e9
14 changed files with 231 additions and 12 deletions

5
.env.development Normal file
View file

@ -0,0 +1,5 @@
PUBLIC_LOGTO_CALLBACK_URL=http://localhost:4321/callback
PUBLIC_LOGTO_REDIRECT_URL=http://localhost:4321
PUBLIC_LOGTO_ENDPOINT=https://auth.app.nbtca.space
PUBLIC_LOGTO_APP_ID=a3tt7oyxeounfiqqugiah
PUBLIC_SITE_URL=http://localhost:4321

6
.env.production Normal file
View file

@ -0,0 +1,6 @@
PUBLIC_LOGTO_CALLBACK_URL=https://nbtca.space/callback
PUBLIC_LOGTO_REDIRECT_URL=https://nbtca.space
PUBLIC_LOGTO_ENDPOINT=https://auth.app.nbtca.space
PUBLIC_LOGTO_APP_ID=a3tt7oyxeounfiqqugiah
PUBLIC_LOGTO_RESOURCE=https://api.nbtca.space
PUBLIC_SITE_URL=https://nbtca.space

6
.gitignore vendored
View file

@ -11,10 +11,8 @@ yarn-debug.log*
yarn-error.log* yarn-error.log*
pnpm-debug.log* pnpm-debug.log*
.env.local
# environment variables .env.*.local
.env
.env.production
# macOS-specific files # macOS-specific files
.DS_Store .DS_Store

View file

@ -8,6 +8,7 @@
], ],
"eslint.experimental.useFlatConfig": true, "eslint.experimental.useFlatConfig": true,
"cSpell.words": [ "cSpell.words": [
"Logto",
"qrcode" "qrcode"
], ],
} }

View file

@ -9,6 +9,9 @@
"astro", "astro",
"astrojs", "astrojs",
"frontmatter", "frontmatter",
"logto",
"logtoClient",
"Logto",
"m_lfit", "m_lfit",
"N3ptune", "N3ptune",
"NBTCA", "NBTCA",

View file

@ -21,6 +21,8 @@
"@fullcalendar/daygrid": "^6.1.11", "@fullcalendar/daygrid": "^6.1.11",
"@fullcalendar/icalendar": "^6.1.11", "@fullcalendar/icalendar": "^6.1.11",
"@fullcalendar/react": "^6.1.11", "@fullcalendar/react": "^6.1.11",
"@headlessui/vue": "^1.7.22",
"@logto/browser": "^2.2.16",
"@stylistic/eslint-plugin": "^2.1.0", "@stylistic/eslint-plugin": "^2.1.0",
"astro": "^4.5.12", "astro": "^4.5.12",
"ical.js": "^1.5.0", "ical.js": "^1.5.0",

View file

@ -29,6 +29,12 @@ dependencies:
'@fullcalendar/react': '@fullcalendar/react':
specifier: ^6.1.11 specifier: ^6.1.11
version: 6.1.14(@fullcalendar/core@6.1.14)(react-dom@18.3.1)(react@18.3.1) version: 6.1.14(@fullcalendar/core@6.1.14)(react-dom@18.3.1)(react@18.3.1)
'@headlessui/vue':
specifier: ^1.7.22
version: 1.7.22(vue@3.4.31)
'@logto/browser':
specifier: ^2.2.16
version: 2.2.16
'@stylistic/eslint-plugin': '@stylistic/eslint-plugin':
specifier: ^2.1.0 specifier: ^2.1.0
version: 2.3.0(eslint@8.57.0)(typescript@5.5.3) version: 2.3.0(eslint@8.57.0)(typescript@5.5.3)
@ -1258,6 +1264,16 @@ packages:
react-dom: 18.3.1(react@18.3.1) react-dom: 18.3.1(react@18.3.1)
dev: false dev: false
/@headlessui/vue@1.7.22(vue@3.4.31):
resolution: {integrity: sha512-Hoffjoolq1rY+LOfJ+B/OvkhuBXXBFgd8oBlN+l1TApma2dB0En0ucFZrwQtb33SmcCqd32EQd0y07oziXWNYg==}
engines: {node: '>=10'}
peerDependencies:
vue: ^3.2.0
dependencies:
'@tanstack/vue-virtual': 3.10.4(vue@3.4.31)
vue: 3.4.31(typescript@5.5.3)
dev: false
/@humanwhocodes/config-array@0.11.14: /@humanwhocodes/config-array@0.11.14:
resolution: {integrity: sha512-3T8LkOmg45BV5FICb15QQMsyUSWrQ8AygVfC7ZG32zOalnqrilm018ZVCw0eapXux8FtA33q8PSRSstjee3jSg==} resolution: {integrity: sha512-3T8LkOmg45BV5FICb15QQMsyUSWrQ8AygVfC7ZG32zOalnqrilm018ZVCw0eapXux8FtA33q8PSRSstjee3jSg==}
engines: {node: '>=10.10.0'} engines: {node: '>=10.10.0'}
@ -1506,6 +1522,30 @@ packages:
'@jridgewell/sourcemap-codec': 1.4.15 '@jridgewell/sourcemap-codec': 1.4.15
dev: false dev: false
/@logto/browser@2.2.16:
resolution: {integrity: sha512-kGodGK8VE3vnbF1PuDronc3YW5PxG9ksQRQJO4FWizCQDStnaYhT8w+ftlhyrZSoyre6hwtuc3fOj2tE+J5e2A==}
dependencies:
'@logto/client': 2.7.3
'@silverhand/essentials': 2.9.1
js-base64: 3.7.7
dev: false
/@logto/client@2.7.3:
resolution: {integrity: sha512-slHm+fPU1vwfKDUNmKmsBHZhWv6pH1DW0p87axy2PEy6/B3aKKQRpW1J/6AhR3JyYfcElmykFGlvr5yT1HZGYQ==}
dependencies:
'@logto/js': 4.1.5
'@silverhand/essentials': 2.9.1
camelcase-keys: 7.0.2
jose: 5.7.0
dev: false
/@logto/js@4.1.5:
resolution: {integrity: sha512-3TpF2WKsjFyt0Gqc4gYV3zGbfDp+2cI6Tp2lGvptCfOn6K4sUccjve3jU7IKih1aACS5CdvlEybg5B7rvcbvpg==}
dependencies:
'@silverhand/essentials': 2.9.1
camelcase-keys: 7.0.2
dev: false
/@nodelib/fs.scandir@2.1.5: /@nodelib/fs.scandir@2.1.5:
resolution: {integrity: sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==} resolution: {integrity: sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==}
engines: {node: '>= 8'} engines: {node: '>= 8'}
@ -1719,6 +1759,11 @@ packages:
resolution: {integrity: sha512-qdiJS5a/QGCff7VUFIqd0hDdWly9rDp8lhVmXVrS11aazX8LOTRLHAXkkEeONNsS43EcCd7gax9LLoOz4vlFQA==} resolution: {integrity: sha512-qdiJS5a/QGCff7VUFIqd0hDdWly9rDp8lhVmXVrS11aazX8LOTRLHAXkkEeONNsS43EcCd7gax9LLoOz4vlFQA==}
dev: false dev: false
/@silverhand/essentials@2.9.1:
resolution: {integrity: sha512-rsql/ZxXMqVvt7ySDHd7xjCog4oCYg+dexxlj3veolajwjKiy/08ZtFyEtFjSEAaKbXwkWZ4TDtiNSxb7HS0yA==}
engines: {node: ^18.12.0 || ^20.9.0, pnpm: ^9.0.0}
dev: false
/@stylistic/eslint-plugin-js@2.3.0(eslint@8.57.0): /@stylistic/eslint-plugin-js@2.3.0(eslint@8.57.0):
resolution: {integrity: sha512-lQwoiYb0Fs6Yc5QS3uT8+T9CPKK2Eoxc3H8EnYJgM26v/DgtW+1lvy2WNgyBflU+ThShZaHm3a6CdD9QeKx23w==} resolution: {integrity: sha512-lQwoiYb0Fs6Yc5QS3uT8+T9CPKK2Eoxc3H8EnYJgM26v/DgtW+1lvy2WNgyBflU+ThShZaHm3a6CdD9QeKx23w==}
engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
@ -1790,6 +1835,19 @@ packages:
- typescript - typescript
dev: false dev: false
/@tanstack/virtual-core@3.10.4:
resolution: {integrity: sha512-yHyli4RHVsI+eJ0RjmOsjA9RpHp3/Zah9t+iRjmFa72dq00TeG/NwuLYuCV6CB4RkWD4i5RD421j1eb6BdKgvQ==}
dev: false
/@tanstack/vue-virtual@3.10.4(vue@3.4.31):
resolution: {integrity: sha512-oikrjnC7BnUCmqh5ptemclUK6EtJj48AdLcJx1t2fTLQyu+60Alo6gPGC3cANgmbEP/1C9DptbeMcm5AAjyBVg==}
peerDependencies:
vue: ^2.7.0 || ^3.0.0
dependencies:
'@tanstack/virtual-core': 3.10.4
vue: 3.4.31(typescript@5.5.3)
dev: false
/@types/babel__core@7.20.5: /@types/babel__core@7.20.5:
resolution: {integrity: sha512-qoQprZvz5wQFJwMDqeseRXWv3rqMvhgpbXFfVyWhbx9X47POIA6i/+dXefEmZKoAgOaTdaIgNSMqMIU61yRyzA==} resolution: {integrity: sha512-qoQprZvz5wQFJwMDqeseRXWv3rqMvhgpbXFfVyWhbx9X47POIA6i/+dXefEmZKoAgOaTdaIgNSMqMIU61yRyzA==}
dependencies: dependencies:
@ -2637,6 +2695,16 @@ packages:
engines: {node: '>= 6'} engines: {node: '>= 6'}
dev: false dev: false
/camelcase-keys@7.0.2:
resolution: {integrity: sha512-Rjs1H+A9R+Ig+4E/9oyB66UC5Mj9Xq3N//vcLf2WzgdTi/3gUu3Z9KoqmlrEG4VuuLK8wJHofxzdQXz/knhiYg==}
engines: {node: '>=12'}
dependencies:
camelcase: 6.3.0
map-obj: 4.3.0
quick-lru: 5.1.1
type-fest: 1.4.0
dev: false
/camelcase@5.3.1: /camelcase@5.3.1:
resolution: {integrity: sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==} resolution: {integrity: sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==}
engines: {node: '>=6'} engines: {node: '>=6'}
@ -4366,6 +4434,14 @@ packages:
hasBin: true hasBin: true
dev: false dev: false
/jose@5.7.0:
resolution: {integrity: sha512-3P9qfTYDVnNn642LCAqIKbTGb9a1TBxZ9ti5zEVEr48aDdflgRjhspWFb6WM4PzAfFbGMJYC4+803v8riCRAKw==}
dev: false
/js-base64@3.7.7:
resolution: {integrity: sha512-7rCnleh0z2CkXhH67J8K1Ytz0b2Y+yxTPL+/KOJoa20hfnVQ/3/T6W/KflYI4bRHRagNeXeU2bkNGI3v1oS/lw==}
dev: false
/js-levenshtein@1.1.6: /js-levenshtein@1.1.6:
resolution: {integrity: sha512-X2BB11YZtrRqY4EnQcLX5Rh373zbK4alC1FW7D7MBhL2gtcC17cTnr6DmfHZeS0s2rTHjUTMMHfG7gO8SSdw+g==} resolution: {integrity: sha512-X2BB11YZtrRqY4EnQcLX5Rh373zbK4alC1FW7D7MBhL2gtcC17cTnr6DmfHZeS0s2rTHjUTMMHfG7gO8SSdw+g==}
engines: {node: '>=0.10.0'} engines: {node: '>=0.10.0'}
@ -4580,6 +4656,11 @@ packages:
'@jridgewell/sourcemap-codec': 1.4.15 '@jridgewell/sourcemap-codec': 1.4.15
dev: false dev: false
/map-obj@4.3.0:
resolution: {integrity: sha512-hdN1wVrZbb29eBGiGjJbeP8JbKjq1urkHJ/LIP/NY48MZ1QVXUsQBV1G1zvYFHn1XE06cwjBsOI2K3Ulnj1YXQ==}
engines: {node: '>=8'}
dev: false
/markdown-table@3.0.3: /markdown-table@3.0.3:
resolution: {integrity: sha512-Z1NL3Tb1M9wH4XESsCDEksWoKTdlUafKc4pt0GRwjUyXaCFZ+dc3g2erqB6zm3szA2IUSi7VnPI+o/9jnxh9hw==} resolution: {integrity: sha512-Z1NL3Tb1M9wH4XESsCDEksWoKTdlUafKc4pt0GRwjUyXaCFZ+dc3g2erqB6zm3szA2IUSi7VnPI+o/9jnxh9hw==}
dev: false dev: false
@ -5628,6 +5709,11 @@ packages:
/queue-microtask@1.2.3: /queue-microtask@1.2.3:
resolution: {integrity: sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==} resolution: {integrity: sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==}
/quick-lru@5.1.1:
resolution: {integrity: sha512-WuyALRjWPDGtt/wzJiadO5AXY+8hZ80hVpe6MyivgraREW751X3SbhRvG3eLKOYN+8VEvqLcf3wdnt44Z4S4SA==}
engines: {node: '>=10'}
dev: false
/react-dom@18.3.1(react@18.3.1): /react-dom@18.3.1(react@18.3.1):
resolution: {integrity: sha512-5m4nQKp+rZRb09LNH59GM4BxTh9251/ylbKIbpe7TpGxfJ+9kv6BLkLBXIjjspbgbnIBNqlI23tRnTWT0snUIw==} resolution: {integrity: sha512-5m4nQKp+rZRb09LNH59GM4BxTh9251/ylbKIbpe7TpGxfJ+9kv6BLkLBXIjjspbgbnIBNqlI23tRnTWT0snUIw==}
peerDependencies: peerDependencies:
@ -6361,6 +6447,11 @@ packages:
resolution: {integrity: sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ==} resolution: {integrity: sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ==}
engines: {node: '>=10'} engines: {node: '>=10'}
/type-fest@1.4.0:
resolution: {integrity: sha512-yGSza74xk0UG8k+pLh5oeoYirvIiWo5t0/o3zHHAO2tRDiZcxWP7fywNlXhqb6/r6sWvwi+RsyQMWhVLe4BVuA==}
engines: {node: '>=10'}
dev: false
/type-fest@2.19.0: /type-fest@2.19.0:
resolution: {integrity: sha512-RAH822pAdBgcNMAfWnCBU3CFZcfZ/i1eZjwFU/dsLKumyuuP3niueg2UAukXYF0E2AAoc82ZSSf9J0WQBinzHA==} resolution: {integrity: sha512-RAH822pAdBgcNMAfWnCBU3CFZcfZ/i1eZjwFU/dsLKumyuuP3niueg2UAukXYF0E2AAoc82ZSSf9J0WQBinzHA==}
engines: {node: '>=12.20'} engines: {node: '>=12.20'}

View file

@ -1,6 +1,7 @@
--- ---
import { SITE_TITLE } from "../consts" import { SITE_TITLE } from "../consts"
import { ref } from "vue" import { ref } from "vue"
import NavigationUser from "./NavigationUser.vue"
const menuList = ref<{ const menuList = ref<{
link: string link: string
@ -10,10 +11,10 @@ const menuList = ref<{
link: "/archive", link: "/archive",
name: "目录", name: "目录",
}, },
{ // {
link: "https://repair.nbtca.space", // link: "https://repair.nbtca.space",
name: "维修", // name: "维修",
}, // },
{ {
link: "/calendar", link: "/calendar",
name: "日历", name: "日历",
@ -50,6 +51,7 @@ const menuList = ref<{
), ),
) )
} }
<NavigationUser client:load />
</div> </div>
</div> </div>
</div> </div>

View file

@ -0,0 +1,73 @@
<script setup lang="ts">
import { onMounted, ref } from "vue"
import { logtoClient } from "../utils/auth"
import type { IdTokenClaims } from "@logto/browser"
import { Menu, MenuButton, MenuItems, MenuItem } from "@headlessui/vue"
const onSignIn = async () => {
logtoClient.signIn(import.meta.env.PUBLIC_LOGTO_CALLBACK_URL)
}
const onSignOut = async () => {
logtoClient.signOut(import.meta.env.PUBLIC_LOGTO_REDIRECT_URL)
}
const isAuthenticated = ref<boolean>()
const userInfo = ref<IdTokenClaims>()
const initAuth = async () => {
try {
await Promise.all([
userInfo.value = await logtoClient.getIdTokenClaims(),
isAuthenticated.value = await logtoClient.isAuthenticated()
])
} catch (error) {
isAuthenticated.value = false
}
}
onMounted(() => {
initAuth()
})
</script>
<template>
<div class="flex items-center justify-center w-12">
<div @click="onSignIn" v-if="isAuthenticated === false" class="">
<a class="nav-item-content px-2 hover:text-[#2997ff] text-nowrap cursor-pointer">登入</a>
</div>
<div class="flex items-center" v-if="isAuthenticated">
<Menu as="div" class="relative inline-block text-left">
<div>
<MenuButton class="flex items-center focus:outline-none">
<div class="h-7 w-7 rounded-full border border-gray-300 overflow-hidden">
<img :src="userInfo.picture" alt="" class="w-full" />
</div>
</MenuButton>
</div>
<transition
enter-active-class="transition duration-100 ease-out"
enter-from-class="transform scale-95 opacity-0"
enter-to-class="transform scale-100 opacity-100"
leave-active-class="transition duration-75 ease-in"
leave-from-class="transform scale-100 opacity-100"
leave-to-class="transform scale-95 opacity-0"
>
<MenuItems
class="absolute right-0 mt-2 w-32 origin-top-right divide-y divide-gray-100 rounded-md bg-white shadow-lg ring-1 ring-black/5 focus:outline-none"
>
<div class="px-1 py-1">
<MenuItem v-slot="{ active }">
<button
@click="onSignOut"
:class="[active ? 'bg-violet-500 text-white' : 'text-gray-900', 'flex w-full items-center rounded-md px-2 py-2 text-sm']"
>
登出
</button>
</MenuItem>
</div>
</MenuItems>
</transition>
</Menu>
</div>
<!-- <div v-if="isAuthenticated" @click="onSignOut" class="px-2">Sign-Out</div> -->
</div>
</template>

View file

@ -2,4 +2,4 @@ export const SITE_TITLE = `拔电关机`
export const SITE_EMAIL = "contact@nbtca.space" export const SITE_EMAIL = "contact@nbtca.space"
export const SITE_NAME = "Computer Association" export const SITE_NAME = "Computer Association"
export const SITE_DESCRIPTION = "Computer Association" export const SITE_DESCRIPTION = "Computer Association"
export const SITE_URL = "https://www.nbtca.space" export const SITE_URL = import.meta.env.PUBLIC_SITE_URL

13
src/env.d.ts vendored
View file

@ -1,2 +1,11 @@
/// <reference path="../.astro/types.d.ts" /> interface ImportMetaEnv {
/// <reference types="astro/client" /> readonly PUBLIC_SITE_URL: string
readonly PUBLIC_LOGTO_CALLBACK_URL: string
readonly PUBLIC_LOGTO_REDIRECT_URL: string
readonly PUBLIC_LOGTO_APP_ID: string
readonly PUBLIC_LOGTO_ENDPOINT: string
}
interface ImportMeta {
readonly env: ImportMetaEnv
}

23
src/pages/callback.astro Normal file
View file

@ -0,0 +1,23 @@
<div></div>
<script>
import { logtoClient } from "../utils/auth"
import LogtoClient from "@logto/browser"
const callbackHandler = async (logtoClient: LogtoClient) => {
try {
await logtoClient.handleSignInCallback(window.location.href)
if (!logtoClient.isAuthenticated) {
window.location.assign("/")
return
}
// Handle successful sign-in
window.location.assign("/")
}
catch (error) {
window.location.assign("/")
}
}
callbackHandler(logtoClient)
</script>

View file

@ -101,7 +101,7 @@ progress {
button { button {
background: none; background: none;
border: 0; border: 0;
box-sizing: content-box; box-sizing: border-box;
color: inherit; color: inherit;
cursor: pointer; cursor: pointer;
font: inherit; font: inherit;

6
src/utils/auth.ts Normal file
View file

@ -0,0 +1,6 @@
import LogtoClient from "@logto/browser"
export const logtoClient = new LogtoClient({
endpoint: import.meta.env.PUBLIC_LOGTO_ENDPOINT,
appId: import.meta.env.PUBLIC_LOGTO_APP_ID,
})