This commit is contained in:
Nadar
2026-03-17 13:24:22 +03:00
commit 82e5ac9d81
554 changed files with 29637 additions and 0 deletions

View File

@@ -0,0 +1,39 @@
import type { NitroFetchRequest } from 'nitropack'
import { callWithNuxt } from '#app'
export function $api<
T = unknown,
R extends NitroFetchRequest = NitroFetchRequest,
>(
request: Parameters<typeof $fetch<T, R>>[0],
options?: Partial<Parameters<typeof $fetch<T, R>>[1]>,
) {
const nuxtApp = useNuxtApp()
const runtimeConfig = useRuntimeConfig()
const cookies = useRequestHeaders(['cookie'])
return $fetch<T, R>(request, {
...options,
headers: {
...options?.headers,
...cookies,
},
retry: false,
baseURL: runtimeConfig.public.apiHost as string,
credentials: 'include',
onResponseError: async ({ response }) => {
if (response.status === 401) {
nuxtApp.runWithContext(() => {
useCookie('session').value = null
})
await callWithNuxt(nuxtApp, clearNuxtState, ['user'])
await callWithNuxt(nuxtApp, navigateTo, ['/login', { redirectCode: 401 }])
}
// setStaticError({
// status: response.status,
// message: nuxtApp.$i18n.t('something_went_wrong'),
// });
},
})
}

View File

@@ -0,0 +1,16 @@
export interface StaticError {
status: number
message?: string
}
export function useStaticError() {
return useState<StaticError | undefined>('static-error')
}
export function setStaticError(value?: StaticError) {
useStaticError().value = value
}
export function clearStaticError() {
clearNuxtState(['static-error'])
}

View File

@@ -0,0 +1,69 @@
export interface User {
id: string
email: string
}
export default () => {
const nuxtApp = useNuxtApp()
const user = useState<User | null>('user')
const authenticated = computed(() => !!user.value)
async function getUser() {
user.value = await $api('/users/current', { method: 'GET' })
}
async function login(email: string, password: string) {
await $api('/sessions', { method: 'POST', body: { email, password } })
await getUser()
navigateTo('/projects')
}
async function register(email: string, password: string) {
await $api('/users', { method: 'POST', body: { email, password } })
navigateTo('/login')
}
async function logout() {
try {
await $api('/sessions', { method: 'delete', body: {} })
}
finally {
clearNuxtState('user')
navigateTo('/login')
}
}
async function requestResetPassword(email: string) {
await $api('/users/password_reset', { method: 'post', body: { email } })
}
async function resetPassword(newPassword: string, resetCode: string) {
await $api('/users/password_reset', {
method: 'put',
body: {
newPassword,
resetCode,
},
})
}
async function resendVerificationCode(email: string) {
await $api('/users/verification', {
method: 'put',
body: { email },
})
}
return {
user,
authenticated,
login,
register,
logout,
resendVerificationCode,
requestResetPassword,
resetPassword,
}
}

View File

@@ -0,0 +1,156 @@
import { computed, provide, reactive, unref } from 'vue'
import type { ComputedRef, InjectionKey, MaybeRef } from 'vue'
import { omit } from 'lodash-es'
import dayjs from 'dayjs'
const DATE_FORMAT = 'DD-MM-YYYY'
export interface Filter {
type?: 'select' | 'calendar'
key: string
label: string
placeholder: string
searchable?: boolean
multiple?: boolean
options?: { label?: string, value: unknown }[]
transform?: (value: AppliedFilter) => AppliedFilters
}
export type Filters = Filter[]
export type AppliedFilter = null | string | string[]
export type AppliedFilters = Record<string, AppliedFilter>
export interface FiltersContext {
schema: MaybeRef<Filters>
appliedFiltersRaw: AppliedFilters
appliedFilters: ComputedRef<AppliedFilters>
empty: boolean | ComputedRef<boolean>
apply: (p1: AppliedFilters | string, p2?: AppliedFilter) => void
reset: () => void
}
export const filtersContextKey: InjectionKey<FiltersContext>
= Symbol('FILTERS')
export default (filters: MaybeRef<Filters>) => {
const url = useRequestURL()
const searchString = computed(() => url.search)
const parsedUrl: { other: Record<string, string>, filters: AppliedFilters }
= reactive({
other: {},
filters: {},
})
const allowedFilters = computed<string[]>(() => {
return unref(filters).map(filter => filter.key)
})
parseUrl(searchString.value)
const appliedFiltersRaw = reactive<AppliedFilters>({
...Object.fromEntries(
allowedFilters.value.map(key => [key, isMultiple(key) ? [] : null]),
),
...parsedUrl.filters,
})
const appliedFilters = computed<AppliedFilters>(() => {
return Object.entries(appliedFiltersRaw).reduce((result, [key, value]) => {
const filter = getFilterByKey(key)!
if (filter.transform) {
const transformedValue = filter.transform(value)
if (transformedValue)
result = { ...result, ...transformedValue }
else
result[key] = value
}
else {
result[key] = value
}
return result
}, {} as AppliedFilters)
})
const empty = computed(() =>
Object.values(appliedFiltersRaw).every((value) => {
return Array.isArray(value) ? value.length === 0 : value !== null
}),
)
function parseUrl(searchString: string) {
const params = new URLSearchParams(searchString)
parsedUrl.other = {}
parsedUrl.filters = {}
for (const [key, value] of Array.from(params.entries())) {
if (allowedFilters.value.includes(key)) {
let newValue = isMultiple(key) ? value.split(',') : value
if (isCalendar(key)) {
newValue = [...newValue].map(date =>
dayjs(date, DATE_FORMAT).valueOf().toString(),
)
}
parsedUrl.filters[key] = newValue
}
else {
parsedUrl.other[key] = value
}
}
parsedUrl.other = omit(parsedUrl.other, ['page'])
}
function apply(p1: AppliedFilters | string, p2?: AppliedFilter) {
if (p2 && typeof p1 === 'string') {
appliedFiltersRaw[p1] = p2
}
else if (typeof p1 === 'object') {
for (const [key, value] of Object.entries(p1))
appliedFiltersRaw[key] = value
}
}
function reset() {
for (const key of Object.keys(appliedFiltersRaw))
appliedFiltersRaw[key] = isMultiple(key) ? [] : null
}
function getFilterByKey(key: string) {
return unref(filters).find(f => f.key === key)
}
function isMultiple(key: string) {
const filter = getFilterByKey(key)
return filter?.multiple ?? filter?.type === 'calendar' ?? false
}
function isCalendar(key: string) {
const filter = getFilterByKey(key)
return filter?.type === 'calendar' ?? false
}
provide(filtersContextKey, {
schema: filters,
appliedFiltersRaw,
appliedFilters,
empty,
apply,
reset,
})
return {
appliedFiltersRaw,
appliedFilters,
empty,
apply,
reset,
}
}