auth
Gitea Actions / Deploy (push) Successful in 27s

This commit is contained in:
vadim committed 2026-10-19 17:24:21 +03:00
1 parent 7639a54c50
commit 2b94bcc20e
11 files changed
+172 -51

No files matched your search

+66
View File
@@ -0,0 +1,66 @@
<script setup lang="ts">
import { onMounted, ref } from 'vue'
import { usePushNotifications } from '@/composables/usePushNotifications.ts'
import NotificationsListElement from '@/components/NotificationsListElement.vue'
const status = ref<NotificationPermission | 'unsupported'>(
'Notification' in window ? Notification.permission : 'unsupported',
)
const loading = ref(false)
const push = usePushNotifications()
async function onClick() {
loading.value = true
try {
status.value = await push.enable()
} finally {
loading.value = false
}
}
onMounted(async () => {
if (!navigator.permissions) return
const perm = await navigator.permissions.query({ name: 'notifications' })
perm.onchange = () => {
status.value = Notification.permission
}
})
</script>
<template>
<div class="d-flex">
<v-menu>
<template v-slot:activator="{ props }">
<v-btn rounded="0" icon v-bind="props" class="">
<v-badge bordered location="top right" color="error" content="9+" :offset-x="1" :offset-y="-3">
<v-icon icon="mdi-bell" />
</v-badge>
</v-btn>
</template>
<v-list width="15rem" border nav density="compact" class="pa-1">
<v-list-item>
<v-sheet class="d-flex align-center ga-2">
<span v-if="status === 'granted' && push.installationId.value"> Уведомления включены </span>
<span v-else-if="status === 'default' || status === 'granted'" class="d-flex flex-column align-center ga-1">
<v-btn
color="primary"
variant="flat"
density="compact"
:disabled="loading"
@click="onClick"
text="Enable notifications"
/>
<span>And choose 'Allow'</span>
</span>
<span v-else-if="status === 'denied'">Notifications blocked</span>
<span v-else>Браузер не поддерживает уведомления</span>
</v-sheet>
</v-list-item>
<v-divider />
<notifications-list-element v-for="(el, idx) in [1, 2, 3, 4, 5]" :key="idx" />
</v-list>
</v-menu>
</div>
</template>
<style scoped lang="scss"></style>
@@ -0,0 +1,12 @@
<script setup lang="ts"></script>
<template>
<v-list-item>
<v-sheet class="d-flex flex-column">
<div>Title</div>
<div>Subtitle</div>
</v-sheet>
</v-list-item>
</template>
<style scoped lang="scss"></style>
-40
View File
@@ -1,40 +0,0 @@
<script setup lang="ts">
import { onMounted, ref } from 'vue'
import { usePushNotifications } from '@/composables/usePushNotifications.ts'
const status = ref<NotificationPermission | 'unsupported'>(
'Notification' in window ? Notification.permission : 'unsupported',
)
const loading = ref(false)
const push = usePushNotifications()
async function onClick() {
loading.value = true
try {
status.value = await push.enable()
} finally {
loading.value = false
}
}
onMounted(async () => {
if (!navigator.permissions) return
const perm = await navigator.permissions.query({ name: 'notifications' })
perm.onchange = () => {
status.value = Notification.permission
}
})
</script>
<template>
<div class="mr-2">
<span v-if="status === 'granted' && push.installationId.value">Уведомления включены</span>
<button v-else-if="status === 'default' || status === 'granted'" :disabled="loading" @click="onClick">
Включить уведомления
</button>
<span v-else-if="status === 'denied'">Уведомления заблокированы.</span>
<span v-else>Браузер не поддерживает уведомления</span>
</div>
</template>
<style scoped lang="scss"></style>
+3 -4
View File
@@ -7,9 +7,9 @@ import BackButton from '@/components/BackButton.vue'
import { useThemeMode } from '@/composables/useThemeMode.ts'
import { THEME_ICONS } from '@/lib/theme.ts'
import NavigationMenu from '@/components/NavigationMenu.vue'
import PushEnable from '@/components/PushEnable.vue'
import { usePushNotifications } from '@/composables/usePushNotifications.ts'
import ProjectMenu from '@/components/ProjectMenu.vue'
import NotificationsList from '@/components/NotificationsList.vue'
const router = useRouter()
const route = useRoute()
@@ -22,7 +22,7 @@ const title = 'Ulitka'
const isAuth = computed(() => !!session.value.data)
function goToSignIn() {
router.push({ path: '/sign-in' })
router.push({ path: '/sign-in-otp' })
}
function goToSignUp() {
@@ -66,13 +66,12 @@ const fluid = computed(() => {
<div v-if="!session.data">
<v-btn @click="goToSignIn">Sign In</v-btn>
<v-btn @click="goToSignUp">Sign Up</v-btn>
</div>
<div v-if="session.data" class="d-flex align-center">
<project-menu />
<push-enable />
<notifications-list />
<v-btn rounded="0" :icon="THEME_ICONS[themeMode]" :title="`Theme: ${themeMode}`" @click="cycleThemeMode" />
+2 -1
View File
@@ -1,6 +1,7 @@
import { createAuthClient } from 'better-auth/vue'
import { emailOTPClient } from 'better-auth/client/plugins'
export const authClient = createAuthClient({
baseURL: window.location.origin,
plugins: [],
plugins: [emailOTPClient()],
})
+7 -1
View File
@@ -4,11 +4,12 @@ import { issuesRoutes } from '@/router/issues.ts'
import HomeView from '@/views/HomeView.vue'
import VerifyEmailView from '@/views/users/VerifyEmailView.vue'
import SignUpView from '@/views/users/SignUpView.vue'
import SignInView from '@/views/users/SignInView.vue'
import { authClient } from '@/lib/auth-client.ts'
import { gamesRoutes } from '@/router/games.ts'
import { settingsRoutes } from '@/router/settings.ts'
import { calendarRoutes } from '@/router/calendar.ts'
import SignInViewOtp from '@/views/users/SignInViewOtp.vue'
import SignInView from '@/views/users/SignInView.vue'
declare module 'vue-router' {
interface RouteMeta {
@@ -29,6 +30,11 @@ const router = createRouter({
component: HomeView,
meta: { layout: 'Default', fluid: true },
},
{
name: 'sign-in-otp',
path: '/sign-in-otp',
component: SignInViewOtp,
},
{
name: 'sign-in',
path: '/sign-in',
+3
View File
@@ -9,9 +9,12 @@ const { ckey } = defineProps<Props>()
</script>
<template>
<div class="d-flex flex-column ga-2">
<div class="text-uppercase text-title-large">Issue: {{ ckey }}</div>
<v-sheet border class="h-100 d-flex flex-column">
<issue-details :ckey="ckey" />
</v-sheet>
</div>
</template>
<style scoped lang="scss"></style>
+1
View File
@@ -203,6 +203,7 @@ async function onDeleteBatch() {
<template>
<div class="d-flex flex-column ga-2 h-100">
<div class="text-uppercase text-title-large">Issues</div>
<issue-filter
:text="text"
:assignee-id="assigneeId"
+1 -4
View File
@@ -26,15 +26,12 @@ const signIn = async () => {
localStorage.removeItem(REMEMBERED_EMAIL_KEY)
}
}
console.log(data)
console.log(error)
}
</script>
<template>
<v-row>
<v-col cols="12" md="6" sm="8" offset-sm="2" offset-md="3" offset="0">
<v-col cols="12" md="4" sm="8" offset-sm="2" offset-md="4" offset="0">
<div class="d-flex flex-column ga-2">
<div class="text-uppercase">sign in</div>
<v-text-field prepend-inner-icon="mdi-email" placeholder="E-mail" v-model="signInData.email" />
+76
View File
@@ -0,0 +1,76 @@
<script setup lang="ts">
import { computed, reactive } from 'vue'
import { authClient } from '@/lib/auth-client.js'
import { useRouter } from 'vue-router'
const REMEMBERED_EMAIL_KEY = 'rememberedEmail'
const router = useRouter()
const signInData = reactive({
email: localStorage.getItem(REMEMBERED_EMAIL_KEY) ?? '',
OTP: '',
rememberMe: true,
showOTP: false,
})
const sendOTP = async () => {
const { data, error } = await authClient.emailOtp.sendVerificationOtp({
email: signInData.email,
type: 'sign-in',
})
if (data?.success) {
if (signInData.rememberMe) localStorage.setItem(REMEMBERED_EMAIL_KEY, signInData.email)
signInData.showOTP = true
}
if (error) {
localStorage.removeItem(REMEMBERED_EMAIL_KEY)
}
}
const checkOTP = async () => {
const { data, error } = await authClient.signIn.emailOtp({
email: signInData.email,
otp: signInData.OTP,
})
console.log({ data, error })
if (data) {
router.push('/')
}
if (error) {
console.log(error)
}
}
</script>
<template>
<v-row justify="center">
<v-col cols="12" lg="3" md="4" sm="8">
<div class="d-flex flex-column ga-2">
<div class="text-uppercase">sign in</div>
<v-text-field
prepend-inner-icon="mdi-email"
density="comfortable"
placeholder="E-mail"
v-model="signInData.email"
/>
<v-otp-input
v-if="signInData.showOTP"
density="comfortable"
autofocus
pattern="numeric"
v-model="signInData.OTP"
/>
<v-btn v-if="signInData.showOTP" prepend-icon="mdi-login" variant="flat" @click="checkOTP" color="primary">
Sign in
</v-btn>
<v-btn v-else prepend-icon="mdi-email-fast-outline" variant="flat" @click="sendOTP"> Send Code </v-btn>
</div>
</v-col>
</v-row>
</template>
<style scoped lang="scss"></style>
+1 -1
View File
@@ -24,7 +24,7 @@ const signUp = async () => {
<template>
<v-row>
<v-col cols="12" md="6" sm="8" offset-sm="2" offset-md="3" offset="0">
<v-col cols="12" md="4" sm="8" offset-sm="2" offset-md="4" offset="0">
<div class="d-flex flex-column ga-2">
<div class="text-uppercase">sign up</div>
<v-text-field placeholder="Name" prepend-inner-icon="mdi-account" v-model="signUpData.name" />