Former-commit-id: 9df4dbc3772273d29024452577f63b8cfa111347 [formerly 29dc4d2e9a85edffbf207e8ee3af21306722c634] [formerly ff49ec6aaef41900de3ac62d321c6af55e17acb5 [formerly 865d23809a689fb95a94210df62ee972fdbf821f]]
Former-commit-id: d45f51f4f143edbddbe17c3d8cf1cb03ffc9d220 [formerly 0db73af8405bc86e7bfc8b42ba046b56145a606b]
Former-commit-id: 4d40862c144b50a0c513d536d9c4814aceb30613
This commit is contained in:
melvinchia3636
2024-04-14 09:53:38 +08:00
parent a14fd1fc21
commit 0f0e624ebc
100 changed files with 669 additions and 597 deletions

View File

@@ -44,8 +44,18 @@ module.exports = {
".ts",
".tsx",
".d.ts"
]
],
"moduleDirectory": ["node_modules", "src/"]
},
"alias": {
"extensions": [".js", ".jsx", ".ts", ".tsx", ".d.ts"],
"map": [
['@components', './src/components/general/'],
['@providers', './src/providers/'],
['@hooks', './src/hooks/'],
['@sidebar', './src/components/Sidebar/'],
],
}
},
"react": {
"version": "detect"

24
keys/cert.pem Normal file
View File

@@ -0,0 +1,24 @@
-----BEGIN CERTIFICATE-----
MIID7zCCAtegAwIBAgIUNXnBYQb9NkWTygrQz59+iVwTP4gwDQYJKoZIhvcNAQEL
BQAwgYYxCzAJBgNVBAYTAk1ZMQ4wDAYDVQQIDAVKb2hvcjEUMBIGA1UEBwwLSm9o
b3IgQmFocnUxETAPBgNVBAoMCENvZGVibG9nMRMwEQYDVQQDDApNRUxWSU5DSElB
MSkwJwYJKoZIhvcNAQkBFhptZWx2aW5jaGlhNjIzNjAwQGdtYWlsLmNvbTAeFw0y
NDA0MTMwODMzNTlaFw0yNTA0MTMwODMzNTlaMIGGMQswCQYDVQQGEwJNWTEOMAwG
A1UECAwFSm9ob3IxFDASBgNVBAcMC0pvaG9yIEJhaHJ1MREwDwYDVQQKDAhDb2Rl
YmxvZzETMBEGA1UEAwwKTUVMVklOQ0hJQTEpMCcGCSqGSIb3DQEJARYabWVsdmlu
Y2hpYTYyMzYwMEBnbWFpbC5jb20wggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEK
AoIBAQCsrWnnNFEeMYoxdsWDdDyMv9WRLczSrKVdW4qdl6ooJSQQ5sRNnm/HbvTv
NnfZzHSYaLIGGZRv81XnQB1WQVkA6izxx9Jc23ZxqyRMSt3NyWFHP+n4SIOQNVBL
OD/0Ite+4qfiHBAjy2E8xWe6UL+sQ1fvZTHXdsGLZimBnkT4nNp68ygsRiRMAeIl
e1SbRxUoJ5/4N2Iicxyq5xUqfbXKI3F0HsYAxpWd1UX89Eh845jG87ImxoXrXBLR
DtskHGxFHPylIl3CoEcIxek8Uw7HfEPE4otGLNpOaM9For3bpOogkMoLChjiCWcY
MOcX5DDQnY0380pntniM2IaNZNKtAgMBAAGjUzBRMB0GA1UdDgQWBBREnrYHXvTb
+IkUaAjjaDIHb0aUGDAfBgNVHSMEGDAWgBREnrYHXvTb+IkUaAjjaDIHb0aUGDAP
BgNVHRMBAf8EBTADAQH/MA0GCSqGSIb3DQEBCwUAA4IBAQAOY//lNrQjFH/dj2ta
Wvj2NqVKWWGX7NZy8kNpzwisfOhs0hkf1WGzUj4uTEthk+SZCgmgi5x9G/QJ7E9Y
gnuVvsa10CJ2rAnlCC83IEx6TO9vkoOWKYwltNTaAXit5o7aajU1eiIBJ1Z6+sUq
+Z1bBGLIHb0csGa5wVYkRhSBaA6wiRGo8KekITG0VXP/f2rQWmTjkMZpBQVNB487
xNs8O4DnRE2mjiWM2EaNeD6zWG18xckUK7Wace9hGDBYCfRR5ANt67I6P/Tj7axp
pbMF5SAUZ9W4RJ0asr1LxGcsAip4vRTD1NgSayCDmU8wQ/tQsOpxHxJIZmFkayhi
SVPx
-----END CERTIFICATE-----

28
keys/key.pem Normal file
View File

@@ -0,0 +1,28 @@
-----BEGIN PRIVATE KEY-----
MIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQCsrWnnNFEeMYox
dsWDdDyMv9WRLczSrKVdW4qdl6ooJSQQ5sRNnm/HbvTvNnfZzHSYaLIGGZRv81Xn
QB1WQVkA6izxx9Jc23ZxqyRMSt3NyWFHP+n4SIOQNVBLOD/0Ite+4qfiHBAjy2E8
xWe6UL+sQ1fvZTHXdsGLZimBnkT4nNp68ygsRiRMAeIle1SbRxUoJ5/4N2Iicxyq
5xUqfbXKI3F0HsYAxpWd1UX89Eh845jG87ImxoXrXBLRDtskHGxFHPylIl3CoEcI
xek8Uw7HfEPE4otGLNpOaM9For3bpOogkMoLChjiCWcYMOcX5DDQnY0380pntniM
2IaNZNKtAgMBAAECggEAC3XlqH21azlDKxiZ7/6VTODSjjR++qCQx8sdPmu5fw6a
qoXWaNxnc3zEGY2rk14ELbVifhB75JjpjlPtXoiEcrttc41H7ffMOwEZLfhuE4wW
aRcvMoEFklr80vE5yryhRb8KAMd3XsN51wG3FQuU8nGfHXlsPxkCVtEsrLinao9K
ZJfxWM+B6FkPlUr4VXrQNnuYE28de+UKwsaMkQHT2Q6AgntM5lKqKM9m0i9xNS37
rH4GYHUG6/Rw5uChv22Qi2V7OlJf9VecRL2UpRtnCwEZ3W5Q1fbNV6fluMYcHwNj
AR3fjbZw2mx6twXWpCke2rx9zm3TgPwIlRCYQyOllQKBgQDuKo7CPbOuSp9Yp7jM
OuAcjo9yhVs0WNPcCbwS7kwABFzTdlsODJeDlCyPr0C+MyqGMrbg50gAF8tBGegd
NeWc56TJFSnZGpxpOZkUJoKS+8mp2IPKx0ezoVnf1+VaoQ7C1txHjOQ6F0egborO
G6CJHhPD+OdXwye7r+shku9hbwKBgQC5m326nomrPRDZq5hWAmbpJJUQh5DZY4lR
3X7vgY+mu2qX5BNdxVJV46oba9bZh3hmQztBb8pdWtUjhl5IsAt0fsKn1RcMKEkk
ZSWSDEvz5R1VNUfriqDrhPG9lAFpIHHRuOO7HblY8aP8uJDAwv4gb+7jB4xfokoZ
VpIDMkxHowKBgQDdZR5UHnt7V88M4Mz+vPQ8V6GteZ4w6Sn4b+wvROnNUkDgKjO2
uanXHNTXtLhglru/CEHMi/L4cyX0nJqTHNkXpJYgftXqBbzEbFCK1MmozPrIModA
50MWDrW3ScDhILrSEF2w/Z3jCmUBFAFgW3o+PIFvOWAEy1uSM5RGH4r7bQKBgGWp
PRxSaaVZNTmEO1QgzK02VE9RcY0gb75fS39zv3LbhG7dhZ3+zjItGKccUfmjsdaR
TIty56xqbAOhPVMi2aNdKtEybgVCq407uXBdO9zCyWY6YMIqlClsD9x+jKdAFheA
5Xs/tJ16/Q8XKHHsW7T4mifPoptfEKcAO8l0a0NxAoGAAW5PkDeLAZIvh0kp+FB4
+I2/ZF9MXBzsmHvHjL183Dp1L8uPKvLJfsiqM1TuT+8CjQU0o9LeaWP2Gl9KBW4y
RONsiJ8RmF8StjYaqiMuQwkeS9duTWC6hdcjc4WHoT+9HJ/fvy9sd21HtFYRplOj
PPn4imkWznFC4DzeUHRJQfg=
-----END PRIVATE KEY-----

30
keys/keytmp.pem Normal file
View File

@@ -0,0 +1,30 @@
-----BEGIN ENCRYPTED PRIVATE KEY-----
MIIFJDBWBgkqhkiG9w0BBQ0wSTAxBgkqhkiG9w0BBQwwJAQQ4x3R3RfiB8csIoHc
ggVb6wICCAAwDAYIKoZIhvcNAgkFADAUBggqhkiG9w0DBwQIaf/S8DLOO7sEggTI
3CcHFPYtq4VixpEMMS3td2qMYc/9JpINOlIOEH3b+qVGdGQVzUY26EpTyVi3nd19
m/RlSwxLdRxOgiFx95FHP7KmarE63AY694SAHItzX6noH5ieqtswDsKKsuaY6m+8
EBhAbMys4f1Rx/qZ5cOKH6YpbMhvKktg4VPLXmY/TwlABIMEquj+U93/UDM5tKXE
6oUux7eBjheQnpmUSkFhwMKaV7m26D/9GA1+ybS2hgomY2uFUcPGChQBJKsaXIjM
j7Ngo2FXVzklqbmmgiIWGnkGTdq/zlKsWPHsr+Nu5cx6pPSKN7eDdvYACpJAo7Hw
rkk9sNsVVRSx8ghOh9wsPZEI+zCl7+YFM9MsTUmV/TSl6M8KgG9l7nj53X59eVl4
zJx5YseuMi47epmb6BQYn9U2p6famJlcdIdry2EMfCp1myemE5JYbLw9zbhDyAy5
VGHZjAskGKkZTdgwP2EDSL12yVMgvcCZOf1BzJLekzJV5BnprHY5vqu/9tn4cI/1
YEukO7ayEObwS+0QD82R91ZHwNLC52/HpkIqfpaTMJWTnLccg5IvtHPMDzv3r2Pa
HjCOWeWhNoaBWwOGK6ldBe2PM+20TS9ExpNnoSQNR62kO/wEOcu6EaBLGdI56kmD
8c2xJEKTza+bbADTBS5PLd6SDYJRPae8ExVIOTrgUomE8LVoL/Ux5D32057GFE/z
TPtcx1A/5XAZbIpW598AZr763hElqi/paEDRsxFnlpY3nF7pJ6aU/gWW4xgYhuug
2o10nSNMm6Fg34vYXsouK5vMEZZlcb4Jst0k8vZMNCSO48nKNuxUaeHmsYL5JVFC
mEfdWY6quwQ1OU78kceGmasz0T3U0OLqY+ytjwAkcWhF4v3STDRVuZMLJ+7dByPC
sYU2i8JOksG6yeuQasv8toQ34QEhixzYJp64j06KgmrgVUyUMrzQV7Sh2gkZPd4Z
BQ+82dDK0p99vBEpCp5oDzHDIxNG/p83Y0cgY6D3Dl4hwrpxcuHmJFF4Wk9bdY/y
s7wgmQrqALI9FaEwkPctpAkXoobw5JRiJHw65a+wCGH05H9vhWiKh6qiPez0sSqa
WzkmDtGvkxcsT8Cs3RZG21wij3M3uQqEkqlEXzZv9YDSffwuuXEwJEfSiEjT2bW4
OprTdTnfeM4M+VtEqTqtK+JTHLoO3X8VvGkciE6qUG4V9/bvVcytEmwfYWoP5JSq
v+JruxLE9HPNKFWuVUv5UZYVckN4DYV0EawHVoOuubOmw1K0vRIvks3vPl146VrO
MoFdy09ZG8RSOS+hdTE39aHE/0H3yWzlv66ZcvQBsNO+L0wPqeZf7uPW9cHMvCR3
TyDVa+kGUE/0Lyq+sm0EjjEVt/aZW7ZPLXBEuhvCDiZMgH4BbyMfiPWIxjmc/AkB
URbR2GNAq3q1+z1VXYj8oTVLyXsQ12AG4gpHDwIAt/KQq3jBsCmMHGCf5iYHnolw
t/vq3xwnxdS7W6qeC2uQpP2ESVc73NXtKYKOcq3C/cVqA6QoPVQmOnywUKkQWejq
ltG3MGHg18VlvpY3yodo3emSGGN7qeSX05lfQm3axcrQbHd5fUII9ZkTDrB4ezTC
5qvopDwCAyJIBKhVFoJluyXpxt/O4Lmu
-----END ENCRYPTED PRIVATE KEY-----

View File

@@ -14,6 +14,7 @@
"@headlessui/react": "^1.7.17",
"@iconify/collections": "^1.0.375",
"@iconify/react": "^4.1.1",
"@passwordless-id/webauthn": "^1.5.0",
"@react-pdf-viewer/core": "3.12.0",
"@types/react-custom-scrollbars": "^4.0.12",
"@types/react-helmet": "^6.1.11",
@@ -31,6 +32,7 @@
"cors": "^2.8.5",
"daisyui": "^4.4.14",
"moment": "^2.30.1",
"path": "^0.12.7",
"pdfjs-dist": "3.4.120",
"pocketbase": "^0.19.0",
"postcss": "^8.4.31",
@@ -64,6 +66,7 @@
"@vitejs/plugin-react": "^4.2.0",
"eslint": "^8.0.1",
"eslint-config-standard-with-typescript": "^40.0.0",
"eslint-import-resolver-alias": "^1.1.2",
"eslint-plugin-import": "^2.29.0",
"eslint-plugin-n": "^15.0.0 || ^16.0.0 ",
"eslint-plugin-promise": "^6.0.0",
@@ -72,6 +75,7 @@
"eslint-plugin-react-refresh": "^0.4.4",
"eslint-plugin-tailwindcss": "^3.14.3",
"typescript": "*",
"vite": "^5.0.0"
"vite": "^5.0.0",
"vite-plugin-mkcert": "^1.17.5"
}
}

View File

@@ -4,11 +4,11 @@ import React from 'react'
import { BrowserRouter } from 'react-router-dom'
import AppRouter from './Router'
import GlobalStateProvider from './providers/GlobalStateProvider'
import GlobalStateProvider from '@providers/GlobalStateProvider'
import { ToastContainer } from 'react-toastify'
import AuthProvider from './providers/AuthProvider'
import PersonalizationProvider from './providers/PersonalizationProvider'
import SpotifyProvider from './providers/SpotifyProvider'
import AuthProvider from '@providers/AuthProvider'
import PersonalizationProvider from '@providers/PersonalizationProvider'
import SpotifyProvider from '@providers/SpotifyProvider'
function App(): React.ReactElement {
return (

View File

@@ -1,5 +1,5 @@
import React from 'react'
import Sidebar from './components/Sidebar'
import Sidebar from '@sidebar'
import Header from './components/Header'
import { Outlet } from 'react-router'

View File

@@ -2,9 +2,9 @@
/* eslint-disable @typescript-eslint/strict-boolean-expressions */
import React, { Suspense, lazy, useContext, useMemo } from 'react'
import { Route, Routes, useLocation, useNavigate } from 'react-router-dom'
import { AuthContext } from './providers/AuthProvider'
import { titleToPath } from './components/Sidebar/components/SidebarItem'
import Loading from './components/general/Loading'
import { AuthContext } from '@providers/AuthProvider'
import { titleToPath } from '@sidebar/components/SidebarItem'
import Loading from '@components/Loading'
import PhotosFavouritesGallery from './modules/Photos/pages/FavouritesGallery'
const PhotosProvider = lazy(

View File

@@ -1,9 +1,10 @@
import React, { useContext, useState } from 'react'
import Input from '../../components/general/Input'
import Input from '@components/Input'
import AuthSignInButton from './AuthSignInButtons'
import { AuthContext } from '../../providers/AuthProvider'
import { AuthContext } from '@providers/AuthProvider'
import { toast } from 'react-toastify'
import { AUTH_ERROR_MESSAGES } from '../../constants/auth'
import * as webauthn from '@passwordless-id/webauthn'
function AuthForm(): React.ReactElement {
const [emailOrUsername, setEmail] = useState('')
@@ -11,11 +12,37 @@ function AuthForm(): React.ReactElement {
const [loading, setLoading] = useState(false)
const {
setAuth,
setUserData,
authenticate,
authWithOauth,
loginQuota: { quota, dismissQuota }
loginQuota: { quota, dismissQuota },
verifyToken
} = useContext(AuthContext)
async function fetchPassKeyChallenge(): Promise<string> {
return await fetch(
`${import.meta.env.VITE_API_HOST}/user/passkey/challenge`,
{
method: 'GET',
headers: {
'Content-Type': 'application/json'
}
}
)
.then(async res => {
const data = await res.json()
if (res.ok && data.state === 'success') {
return data.data
} else {
throw new Error(data.message)
}
})
.catch(err => {
toast.error('Couldnt fetch the challenge. Please try again.')
console.error(err)
})
}
function signIn(): void {
if (emailOrUsername.length === 0 || password.length === 0) {
toast.error(AUTH_ERROR_MESSAGES.EMPTY_FIELDS)
@@ -43,19 +70,91 @@ function AuthForm(): React.ReactElement {
})
}
function signInWithGithub(): void {
setLoading(true)
authWithOauth('github')
.then(res => {
if (!res.startsWith('success')) {
toast.error(res)
async function registerWithPasskey(): Promise<void> {
const res = await webauthn.client.register(
'melvinchia623600@gmail.com',
'20e47b44-293a-417a-8559-d7f32affd8b4',
{
authenticatorType: 'both',
userVerification: 'required',
discoverable: 'preferred',
timeout: 60000,
attestation: true
}
)
await fetch(`${import.meta.env.VITE_API_HOST}/user/passkey/register`, {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify(res)
})
.then(async res => {
const data = await res.json()
if (res.ok && data.state === 'success') {
toast.success('Passkey created successfully')
} else {
toast.success('Welcome back, ' + res.split(' ').slice(1).join(' '))
throw new Error(data.message)
}
})
.catch(err => {
toast.error("Couldn't create the passkey. Please try again.")
console.error(err)
})
.finally(() => {
setLoading(false)
})
.catch(() => {
toast.error(AUTH_ERROR_MESSAGES.UNKNOWN_ERROR)
}
async function signInWithPasskey(): Promise<void> {
setLoading(true)
const challenge = await fetchPassKeyChallenge()
const res = await webauthn.client.authenticate([], challenge)
await fetch(`${import.meta.env.VITE_API_HOST}/user/passkey/login`, {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify(res)
})
.then(async res => {
const data = await res.json()
if (res.ok && data.state === 'success') {
document.cookie = `token=${data.token}; path=/; expires=${new Date(
Date.now() + 7 * 24 * 60 * 60 * 1000
).toUTCString()}`
verifyToken(data.token)
.then(async ({ success, userData }) => {
if (success) {
setUserData(userData)
setAuth(true)
toast.success(`Welcome back, ${userData.name}!`)
}
})
.catch(() => {
setAuth(false)
})
.finally(() => {
setLoading(false)
})
} else {
throw new Error(data.message)
}
})
.catch(err => {
toast.error(
'Failed to authenticate with the passkey. Please try again.'
)
console.error(err)
})
.finally(() => {
setLoading(false)
})
}
@@ -82,6 +181,7 @@ function AuthForm(): React.ReactElement {
signIn()
}
}}
darker
/>
<Input
name="Password"
@@ -95,13 +195,14 @@ function AuthForm(): React.ReactElement {
signIn()
}
}}
darker
/>
<AuthSignInButton
emailOrUsername={emailOrUsername}
password={password}
loading={loading}
signIn={signIn}
signInWithGithub={signInWithGithub}
signInWithPasskey={signInWithPasskey}
/>
</div>
)

View File

@@ -1,5 +1,5 @@
import React, { useContext } from 'react'
import { AuthContext } from '../../providers/AuthProvider'
import { AuthContext } from '@providers/AuthProvider'
import { Icon } from '@iconify/react/dist/iconify.js'
function AuthSignInButton({
@@ -7,13 +7,13 @@ function AuthSignInButton({
password,
loading,
signIn,
signInWithGithub
signInWithPasskey
}: {
emailOrUsername: string
password: string
loading: boolean
signIn: () => void
signInWithGithub: () => void
signInWithPasskey: () => void
}): React.ReactElement {
const { auth } = useContext(AuthContext)
@@ -31,14 +31,21 @@ function AuthSignInButton({
>
{loading ? <Icon icon="svg-spinners:180-ring" /> : 'Sign In'}
</button>
<button
type="button"
onClick={signInWithGithub}
className="flex items-center justify-center gap-3 rounded-lg bg-bg-400 p-6 font-semibold uppercase tracking-widest text-bg-100 transition-all hover:bg-bg-500 dark:bg-bg-800 dark:hover:bg-bg-700"
>
<Icon icon="tabler:brand-github" className="text-2xl" />
Sign In with Github
</button>
<div className="flex items-center gap-3">
<div className="h-[2px] w-full bg-bg-600"></div>
<div className="shrink-0 font-medium text-bg-600">Or Sign In With</div>
<div className="h-[2px] w-full bg-bg-600"></div>
</div>
<div className="flex w-full gap-4">
<button
type="button"
onClick={signInWithPasskey}
className="flex w-full items-center justify-center gap-3 rounded-lg bg-bg-400 p-6 font-semibold uppercase tracking-widest text-bg-100 transition-all hover:bg-bg-500 dark:bg-bg-800 dark:hover:bg-bg-700"
>
<Icon icon="tabler:key" className="text-2xl" />
Passkey
</button>
</div>
</div>
)
}

View File

@@ -1,7 +1,7 @@
import React, { Fragment, useContext } from 'react'
import { Icon } from '@iconify/react'
import { GlobalStateContext } from '../providers/GlobalStateProvider'
import { AuthContext } from '../providers/AuthProvider'
import { GlobalStateContext } from '@providers/GlobalStateProvider'
import { AuthContext } from '@providers/AuthProvider'
import { Menu, Transition } from '@headlessui/react'
import { toast } from 'react-toastify'
import MenuItem from './general/HamburgerMenu/MenuItem'

View File

@@ -1,5 +1,5 @@
import React, { useContext } from 'react'
import { GlobalStateContext } from '../../../providers/GlobalStateProvider'
import { GlobalStateContext } from '@providers/GlobalStateProvider'
import { Icon } from '@iconify/react'
function SidebarHeader(): React.ReactElement {

View File

@@ -2,7 +2,7 @@
/* eslint-disable multiline-ternary */
import { Icon } from '@iconify/react'
import React, { useContext, useEffect, useState } from 'react'
import { GlobalStateContext } from '../../../providers/GlobalStateProvider'
import { GlobalStateContext } from '@providers/GlobalStateProvider'
import { useLocation } from 'react-router'
import { Link } from 'react-router-dom'

View File

@@ -3,13 +3,13 @@
/* eslint-disable @typescript-eslint/no-non-null-assertion */
/* eslint-disable multiline-ternary */
import React, { useContext, useEffect, useState } from 'react'
import { GlobalStateContext } from '../../../providers/GlobalStateProvider'
import { GlobalStateContext } from '@providers/GlobalStateProvider'
import SidebarItem, { titleToPath } from './SidebarItem'
import SidebarTitle from './SidebarTitle'
import SidebarDivider from './SidebarDivider'
import { type INotesWorkspace } from '../../../modules/Notes'
import useFetch from '../../../hooks/useFetch'
import { AuthContext } from '../../../providers/AuthProvider'
import useFetch from '@hooks/useFetch'
import { AuthContext } from '@providers/AuthProvider'
import { ROUTES } from '../../../Router'
function SidebarItems(): React.ReactElement {

View File

@@ -1,6 +1,6 @@
/* eslint-disable multiline-ternary */
import React, { useContext } from 'react'
import { GlobalStateContext } from '../../providers/GlobalStateProvider'
import { GlobalStateContext } from '@providers/GlobalStateProvider'
import SidebarItems from './components/SidebarItems'
import SidebarHeader from './components/SidebarHeader'

View File

@@ -2,7 +2,7 @@
/* eslint-disable multiline-ternary */
import { Icon } from '@iconify/react/dist/iconify.js'
import React, { useContext } from 'react'
import { PersonalizationContext } from '../../providers/PersonalizationProvider'
import { PersonalizationContext } from '@providers/PersonalizationProvider'
function CreateOrModifyButton({
type,

View File

@@ -1,9 +1,9 @@
/* eslint-disable multiline-ternary */
/* eslint-disable @typescript-eslint/indent */
import React from 'react'
import ModuleHeader from '../../components/general/ModuleHeader'
import ModuleHeader from '@components/ModuleHeader'
import { Icon } from '@iconify/react'
import ModuleWrapper from '../../components/general/ModuleWrapper'
import ModuleWrapper from '@components/ModuleWrapper'
function Calendar(): React.ReactElement {
return (

View File

@@ -7,15 +7,14 @@ function LogItemContent({
entry: IChangeLogVersion
}): React.ReactElement {
return (
<ul className="flex list-inside list-disc flex-col gap-2 text-bg-500 dark:text-bg-500">
<ul className="list-inside list-disc space-y-2">
{entry.entries
.sort((a, b) => a.feature.localeCompare(b.feature))
.map(subEntry => (
<li key={subEntry.id}>
<span className="font-semibold text-bg-800 dark:text-bg-100">
{subEntry.feature}:
</span>{' '}
<span className="font-semibold">{subEntry.feature}:</span>{' '}
<span
className="text-bg-500 dark:text-bg-500"
dangerouslySetInnerHTML={{
__html: subEntry.description.replace(
/<code>(.*?)<\/code>/g,

View File

@@ -9,19 +9,17 @@ function LogItemHeader({
return (
<h3 className="mb-2 flex flex-col gap-2 text-2xl font-semibold sm:flex-row sm:items-end">
Ver. {entry.version}{' '}
<span className="mb-0.5 block text-sm">
<span className="mb-0.5 text-sm">
(
{new Date(entry.date_range[0]).toLocaleDateString('en-US', {
year: 'numeric',
month: 'short',
day: 'numeric'
})}{' '}
-{' '}
{new Date(entry.date_range[1]).toLocaleDateString('en-US', {
year: 'numeric',
month: 'short',
day: 'numeric'
})}
{entry.date_range
.map(date =>
new Date(date).toLocaleDateString('en-US', {
year: 'numeric',
month: 'short',
day: 'numeric'
})
)
.join(' - ')}
)
</span>
</h3>

View File

@@ -7,7 +7,7 @@ function LogItem({ entry }: { entry: IChangeLogVersion }): React.ReactElement {
return (
<li
key={entry.version}
className="flex flex-col gap-2 rounded-lg bg-bg-50 p-6 shadow-[4px_4px_10px_0px_rgba(0,0,0,0.05)] dark:bg-bg-900"
className="space-y-2 rounded-lg bg-bg-50 p-6 shadow-[4px_4px_10px_0px_rgba(0,0,0,0.05)] dark:bg-bg-900"
>
<LogItemHeader entry={entry} />
<LogItemContent entry={entry} />

View File

@@ -2,13 +2,13 @@
/* eslint-disable @typescript-eslint/no-throw-literal */
/* eslint-disable @typescript-eslint/indent */
import React, { useEffect, useState } from 'react'
import ModuleHeader from '../../components/general/ModuleHeader'
import useFetch from '../../hooks/useFetch'
import ModuleHeader from '@components/ModuleHeader'
import useFetch from '@hooks/useFetch'
import LogItem from './components/LogItem'
import APIComponentWithFallback from '../../components/general/APIComponentWithFallback'
import ModuleWrapper from '../../components/general/ModuleWrapper'
import EmptyStateScreen from '../../components/general/EmptyStateScreen'
import SearchInput from '../../components/general/SearchInput'
import APIComponentWithFallback from '@components/APIComponentWithFallback'
import ModuleWrapper from '@components/ModuleWrapper'
import EmptyStateScreen from '@components/EmptyStateScreen'
import SearchInput from '@components/SearchInput'
export interface IChangeLogVersion {
version: string
@@ -66,7 +66,7 @@ function Changelog(): React.ReactElement {
stuffToSearch="features"
/>
<APIComponentWithFallback data={data}>
<ul className="my-8 flex flex-1 flex-col gap-4">
<ul className="my-8 space-y-4">
{typeof data !== 'string' &&
(filteredData.length > 0 ? (
filteredData.map(entry => (

View File

@@ -4,7 +4,7 @@ import { Icon } from '@iconify/react/dist/iconify.js'
import React, { useContext, useEffect, useState } from 'react'
import ActivityCalendar from 'react-activity-calendar'
import THEME_COLOR_HEX from '../../../constants/theme_color_hex'
import { PersonalizationContext } from '../../../providers/PersonalizationProvider'
import { PersonalizationContext } from '@providers/PersonalizationProvider'
import { Tooltip } from 'react-tooltip'
function CodeTimeActivityCalendar(): React.ReactElement {
@@ -43,8 +43,8 @@ function CodeTimeActivityCalendar(): React.ReactElement {
}, [])
return (
<div className="mt-16 flex w-full flex-col gap-6">
<h1 className="mb-2 flex items-center gap-2 text-2xl font-semibold">
<div className="space-y-2">
<h1 className="flex items-center gap-2 text-2xl font-semibold">
<Icon icon="tabler:activity" className="text-3xl" />
<span className="ml-2">Activities Calendar</span>
</h1>
@@ -120,7 +120,7 @@ function CodeTimeActivityCalendar(): React.ReactElement {
</div>
<Tooltip id="react-tooltip" className="z-[9999]" />
{firstYear && (
<div className="flex flex-col gap-2">
<div className="space-y-2">
{Array(new Date().getFullYear() - firstYear + 1)
.fill(0)
.map((_, index) => (

View File

@@ -1,115 +0,0 @@
/* eslint-disable @typescript-eslint/restrict-plus-operands */
/* eslint-disable @typescript-eslint/indent */
import { Icon } from '@iconify/react/dist/iconify.js'
import React, { useState } from 'react'
import HoursAndMinutesFromSeconds from './HoursAndMinutesFromSeconds'
import useFetch from '../../../hooks/useFetch'
import APIComponentWithFallback from '../../../components/general/APIComponentWithFallback'
function CodeTimeMostProjects(): React.ReactElement {
const [lastForProjects, setLastForProjects] = useState<
'24 hours' | '7 days' | '30 days'
>('24 hours')
const [topProjects] = useFetch<Record<string, number>>(
`code-time/projects?last=${lastForProjects}`
)
return (
<div className="mt-16 flex w-full flex-col gap-6">
<div className="flex w-full items-center justify-between">
<h1 className="mb-2 flex items-center gap-2 text-2xl font-semibold">
<Icon icon="tabler:clipboard" className="text-3xl" />
<span className="ml-2">
Projects You&apos;ve Spent Most Time Doing
</span>
</h1>
<div className="flex items-center gap-2">
<p className="font-medium tracking-wider">in the last</p>
<div className="flex gap-2 rounded-lg p-2">
{['24 hours', '7 days', '30 days'].map((last, index) => (
<button
key={index}
onClick={() => {
setLastForProjects(last as '24 hours' | '7 days' | '30 days')
}}
className={`rounded-md p-4 px-6 tracking-wide ${
lastForProjects === last
? 'bg-bg-200 font-semibold text-bg-800 dark:bg-bg-700/50 dark:text-bg-100'
: 'text-bg-500 hover:bg-bg-200/50 dark:hover:bg-bg-700/50'
}`}
>
{last}
</button>
))}
</div>
</div>
</div>
<APIComponentWithFallback data={topProjects}>
<div className="flex w-full">
{typeof topProjects !== 'string' &&
Object.keys(topProjects).length > 0 &&
Object.entries(topProjects)
.slice(0, 5)
.map(([key, value], index) => (
<div
className={`h-6 border ${index === 0 && 'rounded-l-lg'} ${
index === 4 && 'rounded-r-lg'
} ${
[
'bg-red-500/20 border-red-500',
'bg-orange-500/20 border-orange-500',
'bg-yellow-500/20 border-yellow-500',
'bg-blue-500/20 border-blue-500',
'bg-emerald-500/20 border-emerald-500'
][index]
}`}
key={key}
style={{
width: `${Math.round(
(value /
Object.entries(topProjects)
.slice(0, 5)
.reduce((a, b) => a + b[1], 0)) *
100
)}%`
}}
></div>
))}
</div>
<ul className="flex flex-col gap-4">
{topProjects !== null &&
Object.keys(topProjects).length > 0 &&
Object.entries(topProjects)
.slice(0, 5)
.map(([key, value], index) => (
<li
key={key}
className="relative flex items-center justify-between gap-4 rounded-lg bg-bg-50 p-6 shadow-[4px_4px_10px_0px_rgba(0,0,0,0.05)] dark:bg-bg-900"
>
<div className="flex items-center gap-4 text-lg font-medium">
<div
className={`h-4 w-4 rounded-md border ${
[
'bg-red-500/20 border-red-500',
'bg-orange-500/20 border-orange-500',
'bg-yellow-500/20 border-yellow-500',
'bg-blue-500/20 border-blue-500',
'bg-emerald-500/20 border-emerald-500'
][index]
} rounded-full`}
></div>
{key}
</div>
<div className="text-3xl font-semibold">
<HoursAndMinutesFromSeconds seconds={value} />
</div>
</li>
))}
</ul>
</APIComponentWithFallback>
</div>
)
}
export default CodeTimeMostProjects

View File

@@ -5,8 +5,8 @@
import { Icon } from '@iconify/react/dist/iconify.js'
import React from 'react'
import HoursAndMinutesFromSeconds from './HoursAndMinutesFromSeconds'
import useFetch from '../../../hooks/useFetch'
import APIComponentWithFallback from '../../../components/general/APIComponentWithFallback'
import useFetch from '@hooks/useFetch'
import APIComponentWithFallback from '@components/APIComponentWithFallback'
function CodeTimeStatistics(): React.ReactElement {
const [stats] = useFetch<Record<string, number>>('code-time/statistics')

View File

@@ -2,25 +2,30 @@
import { Icon } from '@iconify/react/dist/iconify.js'
import React, { useState } from 'react'
import HoursAndMinutesFromSeconds from './HoursAndMinutesFromSeconds'
import useFetch from '../../../hooks/useFetch'
import APIComponentWithFallback from '../../../components/general/APIComponentWithFallback'
import useFetch from '@hooks/useFetch'
import APIComponentWithFallback from '@components/APIComponentWithFallback'
function CodeTimeMostLanguages(): React.ReactElement {
const [lastForLanguages, setLastForLanguages] = useState<
'24 hours' | '7 days' | '30 days'
>('24 hours')
function CodeTimeTopEntries({
type
}: {
type: 'languages' | 'projects'
}): React.ReactElement {
const [lastFor, setLastFor] = useState<'24 hours' | '7 days' | '30 days'>(
'24 hours'
)
const [topLanguages] = useFetch<Record<string, number>>(
`code-time/languages?last=${lastForLanguages}`
const [topEntries] = useFetch<Record<string, number>>(
`code-time/${type}?last=${lastFor}`
)
return (
<div className="mb-6 mt-16 flex w-full flex-col gap-6 pb-6">
<div className="flex w-full items-center justify-between">
<div className="space-y-6 pb-8">
<div className="flex items-center justify-between">
<h1 className="mb-2 flex items-center gap-2 text-2xl font-semibold">
<Icon icon="tabler:code" className="text-3xl" />
<span className="ml-2">
Languages You&apos;ve Spent Most Time Using
{type[0].toUpperCase()}
{type.slice(1)} You&apos;ve Spent Most Time Using
</span>
</h1>
<div className="flex items-center gap-2">
@@ -30,10 +35,10 @@ function CodeTimeMostLanguages(): React.ReactElement {
<button
key={index}
onClick={() => {
setLastForLanguages(last as '24 hours' | '7 days' | '30 days')
setLastFor(last as '24 hours' | '7 days' | '30 days')
}}
className={`rounded-md p-4 px-6 tracking-wide hover:bg-bg-700/50 ${
lastForLanguages === last
lastFor === last
? 'bg-bg-200 font-semibold text-bg-800 dark:bg-bg-700/50 dark:text-bg-100'
: 'text-bg-500 hover:bg-bg-200/50 dark:hover:bg-bg-700/50'
}`}
@@ -44,16 +49,17 @@ function CodeTimeMostLanguages(): React.ReactElement {
</div>
</div>
</div>
<APIComponentWithFallback data={topLanguages}>
<APIComponentWithFallback data={topEntries}>
<div className="flex w-full">
{typeof topLanguages !== 'string' &&
Object.keys(topLanguages).length > 0 &&
Object.entries(topLanguages)
{typeof topEntries !== 'string' &&
Object.keys(topEntries).length > 0 &&
Object.entries(topEntries)
.slice(0, 5)
.map(([key, value], index) => (
<div
className={`h-6 border ${index === 0 && 'rounded-l-lg'} ${
index === 4 && 'rounded-r-lg'
index === Object.entries(topEntries).length - 1 &&
'rounded-r-lg'
} ${
[
'bg-red-500/20 border-red-500',
@@ -67,7 +73,7 @@ function CodeTimeMostLanguages(): React.ReactElement {
style={{
width: `${Math.round(
(value /
Object.entries(topLanguages)
Object.entries(topEntries)
.slice(0, 5)
.reduce((a, b) => a + b[1], 0)) *
100
@@ -77,9 +83,9 @@ function CodeTimeMostLanguages(): React.ReactElement {
))}
</div>
<ul className="flex flex-col gap-4">
{topLanguages !== null &&
Object.keys(topLanguages).length > 0 &&
Object.entries(topLanguages)
{topEntries !== null &&
Object.keys(topEntries).length > 0 &&
Object.entries(topEntries)
.slice(0, 5)
.map(([key, value], index) => (
<li
@@ -111,4 +117,4 @@ function CodeTimeMostLanguages(): React.ReactElement {
)
}
export default CodeTimeMostLanguages
export default CodeTimeTopEntries

View File

@@ -1,11 +1,10 @@
import React from 'react'
import ModuleHeader from '../../components/general/ModuleHeader'
import ModuleHeader from '@components/ModuleHeader'
import CodeTimeStatistics from './components/CodeTimeStatistics'
import CodeTimeActivityCalendar from './components/CodeTimeActivityCalendar'
import CodeTimeMostProjects from './components/CodeTimeMostProjects'
import CodeTimeMostLanguages from './components/CodeTimeMostLanguages'
import ModuleWrapper from '../../components/general/ModuleWrapper'
import CodeTimeTopEntries from './components/CodeTimeTopEntries'
import ModuleWrapper from '@components/ModuleWrapper'
export default function CodeTime(): React.ReactElement {
return (
@@ -14,11 +13,15 @@ export default function CodeTime(): React.ReactElement {
title="Code Time"
desc="See how much time you spend grinding code."
/>
<div className="mt-6 flex min-h-0 w-full flex-1 flex-col items-center">
<div className="mt-8 min-h-0 w-full space-y-12">
<CodeTimeStatistics />
<CodeTimeActivityCalendar />
<CodeTimeMostProjects />
<CodeTimeMostLanguages />
{['projects', 'languages'].map(type => (
<CodeTimeTopEntries
key={type}
type={type as 'projects' | 'languages'}
/>
))}
</div>
</ModuleWrapper>
)

View File

@@ -19,9 +19,9 @@ import {
Title,
Filler
} from 'chart.js'
import ModuleHeader from '../../components/general/ModuleHeader'
import { AuthContext } from '../../providers/AuthProvider'
import ModuleWrapper from '../../components/general/ModuleWrapper'
import ModuleHeader from '@components/ModuleHeader'
import { AuthContext } from '@providers/AuthProvider'
import ModuleWrapper from '@components/ModuleWrapper'
ChartJS.register(
ArcElement,

View File

@@ -1,12 +1,12 @@
import { Icon } from '@iconify/react/dist/iconify.js'
import React, { useEffect, useState } from 'react'
import { useNavigate, useParams } from 'react-router-dom'
import GoBackButton from '../../../components/general/GoBackButton'
import useFetch from '../../../hooks/useFetch'
import GoBackButton from '@components/GoBackButton'
import useFetch from '@hooks/useFetch'
import { type IFlashcardDeck } from '..'
import APIComponentWithFallback from '../../../components/general/APIComponentWithFallback'
import HamburgerMenu from '../../../components/general/HamburgerMenu'
import MenuItem from '../../../components/general/HamburgerMenu/MenuItem'
import APIComponentWithFallback from '@components/APIComponentWithFallback'
import HamburgerMenu from '@components/HamburgerMenu'
import MenuItem from '@components/HamburgerMenu/MenuItem'
import EditCardModal from './EditCardModal'
// Generated by https://quicktype.io

View File

@@ -1,10 +1,10 @@
/* eslint-disable @typescript-eslint/indent */
/* eslint-disable multiline-ternary */
import React, { useEffect, useState } from 'react'
import Modal from '../../../components/general/Modal'
import Modal from '@components/Modal'
import { Icon } from '@iconify/react/dist/iconify.js'
import { type IFlashcardCard } from './CardSet'
import CreateOrModifyButton from '../../../components/general/CreateOrModifyButton'
import CreateOrModifyButton from '@components/CreateOrModifyButton'
import { toast } from 'react-toastify'
import { cookieParse } from 'pocketbase'

View File

@@ -1,13 +1,13 @@
/* eslint-disable @typescript-eslint/strict-boolean-expressions */
/* eslint-disable @typescript-eslint/indent */
import React from 'react'
import SidebarDivider from '../../../components/Sidebar/components/SidebarDivider'
import SidebarTitle from '../../../components/Sidebar/components/SidebarTitle'
import SidebarDivider from '@sidebar/components/SidebarDivider'
import SidebarTitle from '@sidebar/components/SidebarTitle'
import { Icon } from '@iconify/react'
import GoBackButton from '../../../components/general/GoBackButton'
import SidebarItem from '../../../components/Sidebar/components/SidebarItem'
import useFetch from '../../../hooks/useFetch'
import APIComponentWithFallback from '../../../components/general/APIComponentWithFallback'
import GoBackButton from '@components/GoBackButton'
import SidebarItem from '@sidebar/components/SidebarItem'
import useFetch from '@hooks/useFetch'
import APIComponentWithFallback from '@components/APIComponentWithFallback'
export interface IFlashcardTag {
amount: number

View File

@@ -2,12 +2,12 @@ import { faker } from '@faker-js/faker'
import { Icon } from '@iconify/react/dist/iconify.js'
import React, { useState } from 'react'
import { Link } from 'react-router-dom'
import ModuleHeader from '../../components/general/ModuleHeader'
import ModuleWrapper from '../../components/general/ModuleWrapper'
import SearchInput from '../../components/general/SearchInput'
import Sidebar, { type IFlashcardTag } from './components/Sidebar'
import useFetch from '../../hooks/useFetch'
import APIComponentWithFallback from '../../components/general/APIComponentWithFallback'
import ModuleHeader from '@components/ModuleHeader'
import ModuleWrapper from '@components/ModuleWrapper'
import SearchInput from '@components/SearchInput'
import Sidebar, { type IFlashcardTag } from '@sidebar'
import useFetch from '@hooks/useFetch'
import APIComponentWithFallback from '@components/APIComponentWithFallback'
export interface IFlashcardDeck {
card_amount: number
@@ -73,7 +73,7 @@ export default function Flashcards(): React.ReactElement {
key={deck.id}
className="group relative flex flex-col justify-start gap-6 rounded-lg bg-bg-50 p-8 shadow-[4px_4px_10px_0px_rgba(0,0,0,0.05)] hover:bg-bg-200/50 dark:bg-bg-900 dark:hover:bg-bg-800"
>
<div className="flex flex-col gap-2">
<div className="space-y-2">
{deck.tag !== '' && (
<span
className="relative isolate mb-1 w-min whitespace-nowrap rounded-full px-3 py-0.5 text-sm"
@@ -92,7 +92,7 @@ export default function Flashcards(): React.ReactElement {
{deck.card_amount} cards
</p>
</div>
<div className="mt-auto flex flex-col gap-2">
<div className="mt-auto space-y-2">
<progress
className="progress h-2 w-full rounded-lg bg-bg-200 dark:bg-bg-700"
value={faker.datatype.number(100)}
@@ -124,7 +124,7 @@ export default function Flashcards(): React.ReactElement {
key={index}
className="group relative flex flex-col justify-start gap-6 rounded-lg bg-bg-50 p-8 shadow-[4px_4px_10px_0px_rgba(0,0,0,0.05)] transition-all hover:bg-bg-200/50 dark:bg-bg-900 dark:hover:bg-bg-800/50"
>
<div className="flex flex-col gap-2">
<div className="space-y-2">
<p className="text-sm font-medium text-bg-500">
{faker.datatype.number(100)} cards
</p>

View File

@@ -3,8 +3,8 @@ import { Icon } from '@iconify/react/dist/iconify.js'
import React from 'react'
import { Link } from 'react-router-dom'
import { type IIdeaBoxContainer } from '../../..'
import HamburgerMenu from '../../../../../components/general/HamburgerMenu'
import MenuItem from '../../../../../components/general/HamburgerMenu/MenuItem'
import HamburgerMenu from '@components/HamburgerMenu'
import MenuItem from '@components/HamburgerMenu/MenuItem'
function ContainerItem({
container,

View File

@@ -1,17 +1,17 @@
/* eslint-disable @typescript-eslint/no-non-null-assertion */
/* eslint-disable multiline-ternary */
import React, { useEffect, useState } from 'react'
import Modal from '../../../../../components/general/Modal'
import Modal from '@components/Modal'
import { Icon } from '@iconify/react/dist/iconify.js'
import ColorPickerModal from '../../../../../components/general/ColorPicker/ColorPickerModal'
import ColorPickerModal from '@components/ColorPicker/ColorPickerModal'
import { toast } from 'react-toastify'
import { type IIdeaBoxContainer } from '../../..'
import { useDebounce } from '@uidotdev/usehooks'
import IconInput from '../../../../../components/general/IconSelector/IconInput'
import Input from '../../../../../components/general/Input'
import IconSelector from '../../../../../components/general/IconSelector'
import CreateOrModifyButton from '../../../../../components/general/CreateOrModifyButton'
import ColorInput from '../../../../../components/general/ColorPicker/ColorInput'
import IconInput from '@components/IconSelector/IconInput'
import Input from '@components/Input'
import IconSelector from '@components/IconSelector'
import CreateOrModifyButton from '@components/CreateOrModifyButton'
import ColorInput from '@components/ColorPicker/ColorInput'
import { cookieParse } from 'pocketbase'
function ModifyContainerModal({

View File

@@ -3,10 +3,10 @@ import { Icon } from '@iconify/react/dist/iconify.js'
import React from 'react'
import { useNavigate } from 'react-router-dom'
import { type IIdeaBoxContainer } from '../../..'
import GoBackButton from '../../../../../components/general/GoBackButton'
import useFetch from '../../../../../hooks/useFetch'
import HamburgerMenu from '../../../../../components/general/HamburgerMenu'
import MenuItem from '../../../../../components/general/HamburgerMenu/MenuItem'
import GoBackButton from '@components/GoBackButton'
import useFetch from '@hooks/useFetch'
import HamburgerMenu from '@components/HamburgerMenu'
import MenuItem from '@components/HamburgerMenu/MenuItem'
function ContainerHeader({
id,

View File

@@ -4,7 +4,7 @@ import { Icon } from '@iconify/react/dist/iconify.js'
import React from 'react'
import { type IIdeaBoxEntry } from '..'
import { toast } from 'react-toastify'
import MenuItem from '../../../../../components/general/HamburgerMenu/MenuItem'
import MenuItem from '@components/HamburgerMenu/MenuItem'
import { cookieParse } from 'pocketbase'
function EntryContextMenu({

View File

@@ -24,7 +24,7 @@ function EntryLink({
updateIdeaList: () => void
}): React.ReactElement {
return (
<div className="group relative my-4 flex flex-col gap-2 rounded-lg bg-bg-50 p-4 shadow-[4px_4px_10px_0px_rgba(0,0,0,0.05)] dark:bg-bg-900">
<div className="group relative my-4 space-y-2 rounded-lg bg-bg-50 p-4 shadow-[4px_4px_10px_0px_rgba(0,0,0,0.05)] dark:bg-bg-900">
{entry.pinned && (
<Icon
icon="tabler:pin"

View File

@@ -3,14 +3,14 @@
/* eslint-disable @typescript-eslint/strict-boolean-expressions */
/* eslint-disable @typescript-eslint/no-non-null-assertion */
import React, { useEffect, useState, useCallback, useContext } from 'react'
import Modal from '../../../../../../components/general/Modal'
import Modal from '@components/Modal'
import { Icon } from '@iconify/react/dist/iconify.js'
import { useDebounce } from '@uidotdev/usehooks'
import { useDropzone } from 'react-dropzone'
import { toast } from 'react-toastify'
import { type IIdeaBoxEntry } from '../..'
import { PersonalizationContext } from '../../../../../../providers/PersonalizationProvider'
import Input from '../../../../../../components/general/Input'
import { PersonalizationContext } from '@providers/PersonalizationProvider'
import Input from '@components/Input'
import ModalHeader from './components/ModalHeader'
import IdeaContentInput from './components/IdeaContentInput'
import IdeaImagePreview from './components/IdeaImagePreview'

View File

@@ -5,16 +5,16 @@ import React, { useEffect, useState } from 'react'
import { useNavigate, useParams, useSearchParams } from 'react-router-dom'
// @ts-expect-error - no types available
import Column from 'react-columns'
import EmptyStateScreen from '../../../../components/general/EmptyStateScreen'
import EmptyStateScreen from '@components/EmptyStateScreen'
import ModifyIdeaModal from './components/ModifyIdeaModal'
import EntryImage from './components/IdeaEntry/EntryImage'
import EntryText from './components/IdeaEntry/EntryText'
import EntryLink from './components/IdeaEntry/EntryLink'
import ContainerHeader from './components/ContainerHeader'
import FAB from './components/FAB'
import DeleteConfirmationModal from '../../../../components/general/DeleteConfirmationModal'
import useFetch from '../../../../hooks/useFetch'
import APIComponentWithFallback from '../../../../components/general/APIComponentWithFallback'
import DeleteConfirmationModal from '@components/DeleteConfirmationModal'
import useFetch from '@hooks/useFetch'
import APIComponentWithFallback from '@components/APIComponentWithFallback'
export interface IIdeaBoxEntry {
collectionId: string

View File

@@ -1,16 +1,16 @@
/* eslint-disable @typescript-eslint/indent */
/* eslint-disable multiline-ternary */
import React, { useEffect, useState } from 'react'
import ModuleHeader from '../../components/general/ModuleHeader'
import ModuleHeader from '@components/ModuleHeader'
import ModifyContainerModal from './components/Containers/components/ModifyContainerModal'
import { useDebounce } from '@uidotdev/usehooks'
import EmptyStateScreen from '../../components/general/EmptyStateScreen'
import EmptyStateScreen from '@components/EmptyStateScreen'
import Containers from './components/Containers'
import DeleteConfirmationModal from '../../components/general/DeleteConfirmationModal'
import useFetch from '../../hooks/useFetch'
import APIComponentWithFallback from '../../components/general/APIComponentWithFallback'
import ModuleWrapper from '../../components/general/ModuleWrapper'
import SearchInput from '../../components/general/SearchInput'
import DeleteConfirmationModal from '@components/DeleteConfirmationModal'
import useFetch from '@hooks/useFetch'
import APIComponentWithFallback from '@components/APIComponentWithFallback'
import ModuleWrapper from '@components/ModuleWrapper'
import SearchInput from '@components/SearchInput'
export interface IIdeaBoxContainer {
collectionId: string

View File

@@ -3,7 +3,7 @@ import React, { useState } from 'react'
import { type Module } from '.'
import { Switch } from '@headlessui/react'
import { Icon } from '@iconify/react/dist/iconify.js'
import Input from '../../components/general/Input'
import Input from '@components/Input'
function ModuleItem({
module,

View File

@@ -2,11 +2,11 @@
/* eslint-disable @typescript-eslint/strict-boolean-expressions */
/* eslint-disable multiline-ternary */
import React, { useContext } from 'react'
import ModuleHeader from '../../components/general/ModuleHeader'
import ModuleWrapper from '../../components/general/ModuleWrapper'
import { AuthContext } from '../../providers/AuthProvider'
import Loading from '../../components/general/Loading'
import { titleToPath } from '../../components/Sidebar/components/SidebarItem'
import ModuleHeader from '@components/ModuleHeader'
import ModuleWrapper from '@components/ModuleWrapper'
import { AuthContext } from '@providers/AuthProvider'
import Loading from '@components/Loading'
import { titleToPath } from '@sidebar/components/SidebarItem'
import { toast } from 'react-toastify'
import { cookieParse } from 'pocketbase'
import ModuleItem from './ModuleItem'

View File

@@ -1,17 +1,17 @@
/* eslint-disable @typescript-eslint/no-non-null-assertion */
import React from 'react'
import ModuleWrapper from '../../../components/general/ModuleWrapper'
import ModuleWrapper from '@components/ModuleWrapper'
import { Worker, Viewer, type RenderPageProps } from '@react-pdf-viewer/core'
// Import the styles
import '@react-pdf-viewer/core/lib/styles/index.css'
import { useNavigate, useParams } from 'react-router'
import useFetch from '../../../hooks/useFetch'
import APIComponentWithFallback from '../../../components/general/APIComponentWithFallback'
import useFetch from '@hooks/useFetch'
import APIComponentWithFallback from '@components/APIComponentWithFallback'
import { type INotesEntry } from '../Subject'
import { Icon } from '@iconify/react/dist/iconify.js'
import FILE_ICONS from '../../../constants/file_icons'
import GoBackButton from '../../../components/general/GoBackButton'
import GoBackButton from '@components/GoBackButton'
function NotesFile(): React.ReactElement {
const { id } = useParams<{ id: string }>()
@@ -53,10 +53,7 @@ function NotesFile(): React.ReactElement {
<Icon icon="tabler:download" className="text-2xl" />
</button>
<button className="rounded-lg p-4 text-bg-500 transition-all hover:bg-bg-200/50 hover:text-bg-800 dark:hover:bg-bg-800 dark:hover:text-bg-100">
<Icon
icon="tabler:dots-vertical"
u="text-xl sm:text-2xl"
/>
<Icon icon="tabler:dots-vertical" u="text-xl sm:text-2xl" />
</button>
</div>
</div>

View File

@@ -5,11 +5,11 @@ import { Menu, Transition } from '@headlessui/react'
import { Icon } from '@iconify/react/dist/iconify.js'
import React from 'react'
import { Link, useNavigate, useParams } from 'react-router-dom'
import GoBackButton from '../../../../../../components/general/GoBackButton'
import MenuItem from '../../../../../../components/general/HamburgerMenu/MenuItem'
import GoBackButton from '@components/GoBackButton'
import MenuItem from '@components/HamburgerMenu/MenuItem'
import { type INotesEntry, type INotesPath } from '../../..'
import { toast } from 'react-toastify'
import useFetch from '../../../../../../hooks/useFetch'
import useFetch from '@hooks/useFetch'
import { cookieParse } from 'pocketbase'
function DirectoryHeader({

View File

@@ -1,7 +1,7 @@
/* eslint-disable @typescript-eslint/indent */
import React from 'react'
import HamburgerMenu from '../../../../../../../../components/general/HamburgerMenu'
import MenuItem from '../../../../../../../../components/general/HamburgerMenu/MenuItem'
import HamburgerMenu from '@components/HamburgerMenu'
import MenuItem from '@components/HamburgerMenu/MenuItem'
import { type INotesEntry } from '../../../../..'
function EntryMenu({

View File

@@ -4,11 +4,11 @@ import React, { useEffect, useState } from 'react'
import { Icon } from '@iconify/react/dist/iconify.js'
import { toast } from 'react-toastify'
import { useDebounce } from '@uidotdev/usehooks'
import Modal from '../../../../components/general/Modal'
import Modal from '@components/Modal'
import { useParams } from 'react-router'
import { type INotesEntry } from '..'
import Input from '../../../../components/general/Input'
import CreateOrModifyButton from '../../../../components/general/CreateOrModifyButton'
import Input from '@components/Input'
import CreateOrModifyButton from '@components/CreateOrModifyButton'
import { cookieParse } from 'pocketbase'
function ModifyFolderModal({

View File

@@ -6,13 +6,13 @@
/* eslint-disable @typescript-eslint/indent */
import React, { useEffect, useState } from 'react'
import { useNavigate, useParams } from 'react-router-dom'
import EmptyStateScreen from '../../../components/general/EmptyStateScreen'
import EmptyStateScreen from '@components/EmptyStateScreen'
import ModifyFolderModal from './components/ModifyFolderModal'
import DirectoryHeader from './components/Directory/components/DirectoryHeader'
import Directory from './components/Directory'
import DeleteConfirmationModal from '../../../components/general/DeleteConfirmationModal'
import useFetch from '../../../hooks/useFetch'
import APIComponentWithFallback from '../../../components/general/APIComponentWithFallback'
import DeleteConfirmationModal from '@components/DeleteConfirmationModal'
import useFetch from '@hooks/useFetch'
import APIComponentWithFallback from '@components/APIComponentWithFallback'
// Generated by https://quicktype.io

View File

@@ -4,13 +4,13 @@ import React, { useEffect, useState } from 'react'
import { Icon } from '@iconify/react/dist/iconify.js'
import { toast } from 'react-toastify'
import { useDebounce } from '@uidotdev/usehooks'
import Modal from '../../../../components/general/Modal'
import Modal from '@components/Modal'
import { useParams } from 'react-router'
import Input from '../../../../components/general/Input'
import IconInput from '../../../../components/general/IconSelector/IconInput'
import Input from '@components/Input'
import IconInput from '@components/IconSelector/IconInput'
import { type INotesSubject } from './SubjectItem'
import IconSelector from '../../../../components/general/IconSelector'
import CreateOrModifyButton from '../../../../components/general/CreateOrModifyButton'
import IconSelector from '@components/IconSelector'
import CreateOrModifyButton from '@components/CreateOrModifyButton'
import { cookieParse } from 'pocketbase'
function ModifySubjectModal({

View File

@@ -2,8 +2,8 @@
import { Icon } from '@iconify/react/dist/iconify.js'
import React from 'react'
import { Link, useParams } from 'react-router-dom'
import HamburgerMenu from '../../../../components/general/HamburgerMenu'
import MenuItem from '../../../../components/general/HamburgerMenu/MenuItem'
import HamburgerMenu from '@components/HamburgerMenu'
import MenuItem from '@components/HamburgerMenu/MenuItem'
export interface INotesSubject {
workspace: string

View File

@@ -1,18 +1,18 @@
/* eslint-disable @typescript-eslint/indent */
/* eslint-disable multiline-ternary */
import React, { useEffect, useState } from 'react'
import ModuleHeader from '../../../components/general/ModuleHeader'
import ModuleHeader from '@components/ModuleHeader'
import { Icon } from '@iconify/react/dist/iconify.js'
import { useNavigate, useParams } from 'react-router'
import { type INotesWorkspace } from '..'
import EmptyStateScreen from '../../../components/general/EmptyStateScreen'
import EmptyStateScreen from '@components/EmptyStateScreen'
import ModifySubjectModal from './components/ModifySubjectModal'
import GoBackButton from '../../../components/general/GoBackButton'
import GoBackButton from '@components/GoBackButton'
import SubjectItem, { type INotesSubject } from './components/SubjectItem'
import CreateSubjectButton from './components/CreateSubjectButton'
import DeleteConfirmationModal from '../../../components/general/DeleteConfirmationModal'
import useFetch from '../../../hooks/useFetch'
import APIComponentWithFallback from '../../../components/general/APIComponentWithFallback'
import DeleteConfirmationModal from '@components/DeleteConfirmationModal'
import useFetch from '@hooks/useFetch'
import APIComponentWithFallback from '@components/APIComponentWithFallback'
function NotesCategory(): React.ReactElement {
const { workspace } = useParams<{ workspace: string }>()

View File

@@ -1,11 +1,11 @@
import React, { useEffect, useState } from 'react'
import ModuleHeader from '../../components/general/ModuleHeader'
import Loading from '../../components/general/Loading'
import Error from '../../components/general/Error'
import ModuleHeader from '@components/ModuleHeader'
import Loading from '@components/Loading'
import Error from '@components/Error'
import { Icon } from '@iconify/react/dist/iconify.js'
import { Link } from 'react-router-dom'
import useFetch from '../../hooks/useFetch'
import ModuleWrapper from '../../components/general/ModuleWrapper'
import useFetch from '@hooks/useFetch'
import ModuleWrapper from '@components/ModuleWrapper'
export interface INotesWorkspace {
collectionId: string

View File

@@ -1,11 +1,11 @@
/* eslint-disable multiline-ternary */
import React, { useContext, useRef, useState } from 'react'
import Input from '../../components/general/Input'
import Input from '@components/Input'
import { Icon } from '@iconify/react/dist/iconify.js'
import { toast } from 'react-toastify'
import { cookieParse } from 'pocketbase'
import { AuthContext } from '../../providers/AuthProvider'
import Modal from '../../components/general/Modal'
import { AuthContext } from '@providers/AuthProvider'
import Modal from '@components/Modal'
function CreatePassword(): React.ReactElement {
const { setUserData, userData } = useContext(AuthContext)

View File

@@ -1,12 +1,12 @@
/* eslint-disable multiline-ternary */
import React, { useEffect, useState } from 'react'
import Modal from '../../components/general/Modal'
import Modal from '@components/Modal'
import { Icon } from '@iconify/react/dist/iconify.js'
import Input from '../../components/general/Input'
import IconInput from '../../components/general/IconSelector/IconInput'
import IconSelector from '../../components/general/IconSelector'
import ColorInput from '../../components/general/ColorPicker/ColorInput'
import ColorPickerModal from '../../components/general/ColorPicker/ColorPickerModal'
import Input from '@components/Input'
import IconInput from '@components/IconSelector/IconInput'
import IconSelector from '@components/IconSelector'
import ColorInput from '@components/ColorPicker/ColorInput'
import ColorPickerModal from '@components/ColorPicker/ColorPickerModal'
import { toast } from 'react-toastify'
import { cookieParse } from 'pocketbase'

View File

@@ -5,8 +5,8 @@ import { type IPasswordEntry } from '.'
import { Icon } from '@iconify/react/dist/iconify.js'
import { cookieParse } from 'pocketbase'
import { toast } from 'react-toastify'
import HamburgerMenu from '../../components/general/HamburgerMenu'
import MenuItem from '../../components/general/HamburgerMenu/MenuItem'
import HamburgerMenu from '@components/HamburgerMenu'
import MenuItem from '@components/HamburgerMenu/MenuItem'
function PasswordEntryITem({
password,

View File

@@ -1,18 +1,18 @@
/* eslint-disable @typescript-eslint/no-non-null-assertion */
/* eslint-disable multiline-ternary */
import React, { useContext, useState } from 'react'
import ModuleWrapper from '../../components/general/ModuleWrapper'
import ModuleHeader from '../../components/general/ModuleHeader'
import Input from '../../components/general/Input'
import ModuleWrapper from '@components/ModuleWrapper'
import ModuleHeader from '@components/ModuleHeader'
import Input from '@components/Input'
import { Icon } from '@iconify/react/dist/iconify.js'
import { AuthContext } from '../../providers/AuthProvider'
import { AuthContext } from '@providers/AuthProvider'
import CreatePassword from './CreatePassword'
import { toast } from 'react-toastify'
import { cookieParse } from 'pocketbase'
import CreatePasswordModal from './CreatePasswordModal'
import EmptyStateScreen from '../../components/general/EmptyStateScreen'
import useFetch from '../../hooks/useFetch'
import APIComponentWithFallback from '../../components/general/APIComponentWithFallback'
import EmptyStateScreen from '@components/EmptyStateScreen'
import useFetch from '@hooks/useFetch'
import APIComponentWithFallback from '@components/APIComponentWithFallback'
import PasswordEntryITem from './PasswordEntryITem'
export interface IPasswordEntry {

View File

@@ -1,5 +1,5 @@
import React, { useContext } from 'react'
import { PersonalizationContext } from '../../../providers/PersonalizationProvider'
import { PersonalizationContext } from '@providers/PersonalizationProvider'
const COLORS = ['bg-slate', 'bg-gray', 'bg-zinc', 'bg-neutral', 'bg-stone']

View File

@@ -1,5 +1,5 @@
import React, { Fragment, useContext } from 'react'
import { PersonalizationContext } from '../../../providers/PersonalizationProvider'
import { PersonalizationContext } from '@providers/PersonalizationProvider'
import { Listbox, Transition } from '@headlessui/react'
import { Icon } from '@iconify/react/dist/iconify.js'

View File

@@ -1,5 +1,5 @@
import React, { useContext } from 'react'
import { PersonalizationContext } from '../../../providers/PersonalizationProvider'
import { PersonalizationContext } from '@providers/PersonalizationProvider'
import { Icon } from '@iconify/react/dist/iconify.js'
function ThemeSelector(): React.ReactElement {

View File

@@ -1,9 +1,9 @@
import React from 'react'
import ModuleHeader from '../../components/general/ModuleHeader'
import ModuleHeader from '@components/ModuleHeader'
import ThemeSelector from './components/ThemeSelector'
import ThemeColorSelector from './components/ThemeColorSelector'
import BgTempSelector from './components/BgTempSelector'
import ModuleWrapper from '../../components/general/ModuleWrapper'
import ModuleWrapper from '@components/ModuleWrapper'
function Personalization(): React.ReactElement {
return (

View File

@@ -7,12 +7,12 @@ import {
import { LazyLoadImage } from 'react-lazy-load-image-component'
import { Icon } from '@iconify/react/dist/iconify.js'
import Zoom from 'react-medium-image-zoom'
import HamburgerMenu from '../../../components/general/HamburgerMenu'
import MenuItem from '../../../components/general/HamburgerMenu/MenuItem'
import HamburgerMenu from '@components/HamburgerMenu'
import MenuItem from '@components/HamburgerMenu/MenuItem'
import { cookieParse } from 'pocketbase'
import { toast } from 'react-toastify'
import { useParams } from 'react-router'
import useFetch from '../../../hooks/useFetch'
import useFetch from '@hooks/useFetch'
function forceDown(url: string, filename: string): void {
fetch(url)

View File

@@ -1,13 +1,13 @@
/* eslint-disable multiline-ternary */
/* eslint-disable @typescript-eslint/indent */
import React, { useContext } from 'react'
import SidebarItem from '../../../components/Sidebar/components/SidebarItem'
import SidebarDivider from '../../../components/Sidebar/components/SidebarDivider'
import SidebarTitle from '../../../components/Sidebar/components/SidebarTitle'
import SidebarItem from '@sidebar/components/SidebarItem'
import SidebarDivider from '@sidebar/components/SidebarDivider'
import SidebarTitle from '@sidebar/components/SidebarTitle'
import { Icon } from '@iconify/react/dist/iconify.js'
import APIComponentWithFallback from '../../../components/general/APIComponentWithFallback'
import APIComponentWithFallback from '@components/APIComponentWithFallback'
import { Link, useNavigate } from 'react-router-dom'
import { PhotosContext } from '../../../providers/PhotosProvider'
import { PhotosContext } from '@providers/PhotosProvider'
function PhotosSidebar(): React.ReactElement {
const {

View File

@@ -1,9 +1,9 @@
/* eslint-disable multiline-ternary */
import React, { useContext, useEffect, useState } from 'react'
import Modal from '../../../../components/general/Modal'
import Modal from '@components/Modal'
import { Icon } from '@iconify/react/dist/iconify.js'
import { PhotosContext } from '../../../../providers/PhotosProvider'
import APIComponentWithFallback from '../../../../components/general/APIComponentWithFallback'
import { PhotosContext } from '@providers/PhotosProvider'
import APIComponentWithFallback from '@components/APIComponentWithFallback'
import { cookieParse } from 'pocketbase'
import { toast } from 'react-toastify'

View File

@@ -4,8 +4,8 @@ import React, { useContext, useState } from 'react'
import { toast } from 'react-toastify'
import { Icon } from '@iconify/react/dist/iconify.js'
import { cookieParse } from 'pocketbase'
import Modal from '../../../../components/general/Modal'
import { PhotosContext } from '../../../../providers/PhotosProvider'
import Modal from '@components/Modal'
import { PhotosContext } from '@providers/PhotosProvider'
function DeletePhotosConfirmationModal({
refreshPhotos,

View File

@@ -1,8 +1,8 @@
import React, { useContext, useEffect, useRef, useState } from 'react'
import Modal from '../../../../components/general/Modal'
import Modal from '@components/Modal'
import { Icon } from '@iconify/react/dist/iconify.js'
import Input from '../../../../components/general/Input'
import CreateOrModifyButton from '../../../../components/general/CreateOrModifyButton'
import Input from '@components/Input'
import CreateOrModifyButton from '@components/CreateOrModifyButton'
import { toast } from 'react-toastify'
import { cookieParse } from 'pocketbase'
import {

View File

@@ -4,8 +4,8 @@ import React, { useContext, useState } from 'react'
import { toast } from 'react-toastify'
import { Icon } from '@iconify/react/dist/iconify.js'
import { cookieParse } from 'pocketbase'
import Modal from '../../../../components/general/Modal'
import { PhotosContext } from '../../../../providers/PhotosProvider'
import Modal from '@components/Modal'
import { PhotosContext } from '@providers/PhotosProvider'
function RemovePhotosFromAlbumConfirmationModal({
refreshPhotos,

View File

@@ -2,12 +2,12 @@
/* eslint-disable @typescript-eslint/no-non-null-assertion */
/* eslint-disable multiline-ternary */
import React, { useContext, useEffect } from 'react'
import ModuleWrapper from '../../../../components/general/ModuleWrapper'
import ModuleWrapper from '@components/ModuleWrapper'
import { Icon } from '@iconify/react/dist/iconify.js'
import { useNavigate, useParams } from 'react-router'
import GoBackButton from '../../../../components/general/GoBackButton'
import useFetch from '../../../../hooks/useFetch'
import APIComponentWithFallback from '../../../../components/general/APIComponentWithFallback'
import GoBackButton from '@components/GoBackButton'
import useFetch from '@hooks/useFetch'
import APIComponentWithFallback from '@components/APIComponentWithFallback'
import {
type IPhotosEntryDimensionsItem,
type IPhotosAlbum,
@@ -19,8 +19,8 @@ import moment from 'moment'
import BottomBar from '../../components/BottomBar'
import DeletePhotosConfirmationModal from '../../components/modals/DeletePhotosConfirmationModal.tsx'
import RemovePhotosFromAlbumConfirmationModal from '../../components/modals/RemovePhotosFromAlbumConfirmationModal.tsx.tsx'
import HamburgerMenu from '../../../../components/general/HamburgerMenu/index.tsx'
import MenuItem from '../../../../components/general/HamburgerMenu/MenuItem.tsx'
import HamburgerMenu from '@components/HamburgerMenu/index.tsx'
import MenuItem from '@components/HamburgerMenu/MenuItem.tsx'
import ModifyAlbumModal from '../../components/modals/ModifyAlbumModal.tsx'
export interface IPhotoAlbumEntryItem extends IPhotosEntryDimensionsItem {

View File

@@ -2,8 +2,8 @@
import { Icon } from '@iconify/react/dist/iconify.js'
import React, { useContext } from 'react'
import { Link } from 'react-router-dom'
import HamburgerMenu from '../../../../../components/general/HamburgerMenu'
import MenuItem from '../../../../../components/general/HamburgerMenu/MenuItem'
import HamburgerMenu from '@components/HamburgerMenu'
import MenuItem from '@components/HamburgerMenu/MenuItem'
import {
PhotosContext,
type IPhotosAlbum

View File

@@ -1,5 +1,5 @@
import React, { useContext } from 'react'
import { PhotosContext } from '../../../../../providers/PhotosProvider'
import { PhotosContext } from '@providers/PhotosProvider'
import { Icon } from '@iconify/react/dist/iconify.js'
function AlbumListHeader(): React.ReactElement {

View File

@@ -1,21 +1,21 @@
/* eslint-disable multiline-ternary */
/* eslint-disable @typescript-eslint/indent */
import React, { useContext, useEffect, useState } from 'react'
import ModuleWrapper from '../../../../components/general/ModuleWrapper'
import ModuleHeader from '../../../../components/general/ModuleHeader'
import ModuleWrapper from '@components/ModuleWrapper'
import ModuleHeader from '@components/ModuleHeader'
import PhotosSidebar from '../../components/PhotosSidebar'
import {
type IPhotosAlbum,
PhotosContext
} from '../../../../providers/PhotosProvider'
import APIComponentWithFallback from '../../../../components/general/APIComponentWithFallback'
import APIComponentWithFallback from '@components/APIComponentWithFallback'
import ModifyAlbumModal from '../../components/modals/ModifyAlbumModal'
import DeleteConfirmationModal from '../../../../components/general/DeleteConfirmationModal'
import DeleteConfirmationModal from '@components/DeleteConfirmationModal'
import AlbumItem from './components/AlbumItem'
import AlbumListHeader from './components/AlbumListHeader'
import SearchInput from '../../../../components/general/SearchInput'
import SearchInput from '@components/SearchInput'
import { useDebounce } from '@uidotdev/usehooks'
import EmptyStateScreen from '../../../../components/general/EmptyStateScreen'
import EmptyStateScreen from '@components/EmptyStateScreen'
function PhotosAlbumList(): React.ReactElement {
const { albumList, refreshAlbumList, refreshPhotos } =

View File

@@ -2,12 +2,12 @@
/* eslint-disable @typescript-eslint/no-non-null-assertion */
/* eslint-disable multiline-ternary */
import React, { useContext, useEffect } from 'react'
import ModuleWrapper from '../../../../components/general/ModuleWrapper.tsx'
import ModuleWrapper from '@components/ModuleWrapper.tsx'
import { Icon } from '@iconify/react/dist/iconify.js'
import { useNavigate, useParams } from 'react-router'
import GoBackButton from '../../../../components/general/GoBackButton.tsx'
import useFetch from '../../../../hooks/useFetch.ts'
import APIComponentWithFallback from '../../../../components/general/APIComponentWithFallback.tsx'
import GoBackButton from '@components/GoBackButton.tsx'
import useFetch from '@hooks/useFetch.ts'
import APIComponentWithFallback from '@components/APIComponentWithFallback.tsx'
import {
type IPhotosEntryDimensionsItem,
PhotosContext
@@ -15,8 +15,8 @@ import {
import Gallery from 'react-photo-gallery'
import ImageObject from '../../components/ImageObject.tsx'
import BottomBar from '../../components/BottomBar.tsx'
import HamburgerMenu from '../../../../components/general/HamburgerMenu/index.tsx'
import MenuItem from '../../../../components/general/HamburgerMenu/MenuItem.tsx'
import HamburgerMenu from '@components/HamburgerMenu/index.tsx'
import MenuItem from '@components/HamburgerMenu/MenuItem.tsx'
export interface IPhotoAlbumEntryItem extends IPhotosEntryDimensionsItem {
collectionId: string

View File

@@ -11,7 +11,7 @@ import {
PhotosContext,
type IPhotosEntry
} from '../../../../../providers/PhotosProvider'
import useOnScreen from '../../../../../hooks/useOnScreen'
import useOnScreen from '@hooks/useOnScreen'
import { cookieParse } from 'pocketbase'
import { toast } from 'react-toastify'

View File

@@ -2,9 +2,9 @@
/* eslint-disable @typescript-eslint/indent */
import React, { useContext, useEffect } from 'react'
import DateGroup from './DateGroup'
import { PhotosContext } from '../../../../../providers/PhotosProvider'
import { PhotosContext } from '@providers/PhotosProvider'
import BottomBar from '../../../components/BottomBar'
import EmptyStateScreen from '../../../../../components/general/EmptyStateScreen'
import EmptyStateScreen from '@components/EmptyStateScreen'
function Gallery(): React.ReactElement {
const { photos, selectedPhotos, setSelectedPhotos } =

View File

@@ -2,11 +2,11 @@
/* eslint-disable @typescript-eslint/indent */
import React, { useContext } from 'react'
import moment from 'moment'
import APIComponentWithFallback from '../../../../../components/general/APIComponentWithFallback'
import APIComponentWithFallback from '@components/APIComponentWithFallback'
import MobileSlidingScrollbar from '../Scrollbars/MobileSlidingScrollbar'
import TimelineScrollbar from '../Scrollbars/TimelineScrollbar'
import Gallery from './Gallery'
import { PhotosContext } from '../../../../../providers/PhotosProvider'
import { PhotosContext } from '@providers/PhotosProvider'
function GalleryContainer(): React.ReactElement {
const {

View File

@@ -3,12 +3,12 @@
/* eslint-disable react/jsx-no-undef */
import { Icon } from '@iconify/react/dist/iconify.js'
import React, { useContext, useEffect, useState } from 'react'
import { PhotosContext } from '../../../../../providers/PhotosProvider'
import useFetch from '../../../../../hooks/useFetch'
import { PhotosContext } from '@providers/PhotosProvider'
import useFetch from '@hooks/useFetch'
import { cookieParse } from 'pocketbase'
import { toast } from 'react-toastify'
import HamburgerMenu from '../../../../../components/general/HamburgerMenu'
import MenuItem from '../../../../../components/general/HamburgerMenu/MenuItem'
import HamburgerMenu from '@components/HamburgerMenu'
import MenuItem from '@components/HamburgerMenu/MenuItem'
function GalleryHeader(): React.ReactElement {
const { refreshPhotos, hidePhotosInAlbum, setHidePhotosInAlbum, setReady } =

View File

@@ -3,7 +3,7 @@
/* eslint-disable @typescript-eslint/no-non-null-assertion */
import { Icon } from '@iconify/react/dist/iconify.js'
import React, { useContext } from 'react'
import { PhotosContext } from '../../../../../providers/PhotosProvider'
import { PhotosContext } from '@providers/PhotosProvider'
function MobileSlidingScrollbar(): React.ReactElement {
const { galleryWrapperRef, sideSliderRef, mobileDateDisplayRef, photos } =

View File

@@ -2,7 +2,7 @@
/* eslint-disable @typescript-eslint/no-non-null-assertion */
import moment from 'moment'
import React, { useContext, useRef } from 'react'
import { PhotosContext } from '../../../../../providers/PhotosProvider'
import { PhotosContext } from '@providers/PhotosProvider'
function TimelineScrollbar(): React.ReactElement {
const {

View File

@@ -1,14 +1,14 @@
/* eslint-disable multiline-ternary */
import React, { useContext, useEffect, useState } from 'react'
import ModuleHeader from '../../../../components/general/ModuleHeader'
import ModuleHeader from '@components/ModuleHeader'
import GalleryHeader from './Gallery/GalleryHeader'
import PhotosSidebar from '../../components/PhotosSidebar'
import GalleryContainer from './Gallery/GalleryContainer'
import ModifyAlbumModal from '../../components/modals/ModifyAlbumModal'
import AddPhotosToAlbumModal from '../../components/modals/AddPhotosToAlbumModal'
import DeletePhotosConfirmationModal from '../../components/modals/DeletePhotosConfirmationModal'
import { PhotosContext } from '../../../../providers/PhotosProvider'
import { GlobalStateContext } from '../../../../providers/GlobalStateProvider'
import { PhotosContext } from '@providers/PhotosProvider'
import { GlobalStateContext } from '@providers/GlobalStateProvider'
function PhotosMainGallery(): React.ReactElement {
const { sidebarExpanded } = useContext(GlobalStateContext)

View File

@@ -1,9 +1,9 @@
/* eslint-disable multiline-ternary */
import React from 'react'
import ModuleHeader from '../../components/general/ModuleHeader'
import ModuleHeader from '@components/ModuleHeader'
import { Icon } from '@iconify/react/dist/iconify.js'
import Timer from './components/Timer'
import ModuleWrapper from '../../components/general/ModuleWrapper'
import ModuleWrapper from '@components/ModuleWrapper'
export default function PomodoroTimer(): React.ReactElement {
return (

View File

@@ -3,15 +3,15 @@
import React, { Fragment, useEffect } from 'react'
import { Icon } from '@iconify/react'
import { useLocation, useNavigate, useParams } from 'react-router-dom'
import GoBackButton from '../../../../components/general/GoBackButton'
import GoBackButton from '@components/GoBackButton'
import { PROJECT_STATUS, type IProjectsKEntry } from '../ProjectList'
import useFetch from '../../../../hooks/useFetch'
import useFetch from '@hooks/useFetch'
import ProjectProgress from './sections/ProjectProgress'
import ProjectFiles from './sections/ProjectFiles'
import { Listbox, Transition } from '@headlessui/react'
import { toast } from 'react-toastify'
import { cookieParse } from 'pocketbase'
import APIComponentWithFallback from '../../../../components/general/APIComponentWithFallback'
import APIComponentWithFallback from '@components/APIComponentWithFallback'
export interface IProjectsKVersion {
collectionId: string

View File

@@ -4,15 +4,15 @@
/* eslint-disable multiline-ternary */
import React, { useState } from 'react'
import { type IProjectsKEntry } from '../../ProjectList'
import APIComponentWithFallback from '../../../../../components/general/APIComponentWithFallback'
import APIComponentWithFallback from '@components/APIComponentWithFallback'
import { Icon } from '@iconify/react/dist/iconify.js'
import EmptyStateScreen from '../../../../../components/general/EmptyStateScreen'
import EmptyStateScreen from '@components/EmptyStateScreen'
import { toast } from 'react-toastify'
import { cookieParse } from 'pocketbase'
import FILE_ICONS from '../../../../../constants/file_icons'
import useFetch from '../../../../../hooks/useFetch'
import HamburgerMenu from '../../../../../components/general/HamburgerMenu'
import MenuItem from '../../../../../components/general/HamburgerMenu/MenuItem'
import useFetch from '@hooks/useFetch'
import HamburgerMenu from '@components/HamburgerMenu'
import MenuItem from '@components/HamburgerMenu/MenuItem'
export default function ProjectFiles({
projectData,

View File

@@ -2,8 +2,8 @@
import React from 'react'
import { Icon } from '@iconify/react/dist/iconify.js'
import { type IProjectsKEntry } from '../../ProjectList'
import APIComponentWithFallback from '../../../../../components/general/APIComponentWithFallback'
import useFetch from '../../../../../hooks/useFetch'
import APIComponentWithFallback from '@components/APIComponentWithFallback'
import useFetch from '@hooks/useFetch'
import { toast } from 'react-toastify'
import { cookieParse } from 'pocketbase'
@@ -96,7 +96,7 @@ export default function ProjectProgress({
</span>
</p>
</div>
<ul className="mt-6 flex flex-col gap-2">
<ul className="mt-6 space-y-2">
{progress.steps.map((id: string, index: number) => (
<li
key={id}

View File

@@ -4,13 +4,13 @@
import React, { Fragment, useState } from 'react'
import { Icon } from '@iconify/react/dist/iconify.js'
import { toast } from 'react-toastify'
import Modal from '../../../../../components/general/Modal'
import CreateOrModifyButton from '../../../../../components/general/CreateOrModifyButton'
import Input from '../../../../../components/general/Input'
import Modal from '@components/Modal'
import CreateOrModifyButton from '@components/CreateOrModifyButton'
import Input from '@components/Input'
import { cookieParse } from 'pocketbase'
import useFetch from '../../../../../hooks/useFetch'
import useFetch from '@hooks/useFetch'
import { type Step } from '../../ProjectEntry/sections/ProjectProgress'
import APIComponentWithFallback from '../../../../../components/general/APIComponentWithFallback'
import APIComponentWithFallback from '@components/APIComponentWithFallback'
import { Listbox, Transition } from '@headlessui/react'
import { PROJECT_STATUS } from '..'

View File

@@ -2,10 +2,10 @@
import { Icon } from '@iconify/react/dist/iconify.js'
import React from 'react'
import { type IProjectsKEntry, PROJECT_STATUS } from '..'
import SidebarDivider from '../../../../../components/Sidebar/components/SidebarDivider'
import SidebarItem from '../../../../../components/Sidebar/components/SidebarItem'
import SidebarTitle from '../../../../../components/Sidebar/components/SidebarTitle'
import APIComponentWithFallback from '../../../../../components/general/APIComponentWithFallback'
import SidebarDivider from '@sidebar/components/SidebarDivider'
import SidebarItem from '@sidebar/components/SidebarItem'
import SidebarTitle from '@sidebar/components/SidebarTitle'
import APIComponentWithFallback from '@components/APIComponentWithFallback'
import { useSearchParams } from 'react-router-dom'
function Sidebar({

View File

@@ -2,18 +2,18 @@
/* eslint-disable multiline-ternary */
/* eslint-disable @typescript-eslint/indent */
import React, { useEffect, useState } from 'react'
import ModuleHeader from '../../../../components/general/ModuleHeader'
import ModuleWrapper from '../../../../components/general/ModuleWrapper'
import useFetch from '../../../../hooks/useFetch'
import APIComponentWithFallback from '../../../../components/general/APIComponentWithFallback'
import ModuleHeader from '@components/ModuleHeader'
import ModuleWrapper from '@components/ModuleWrapper'
import useFetch from '@hooks/useFetch'
import APIComponentWithFallback from '@components/APIComponentWithFallback'
import { useSearchParams } from 'react-router-dom'
import { type IProjectsKProgress } from '../ProjectEntry/sections/ProjectProgress'
import CreateProjectModal from './components/CreateProjectModal'
import EmptyStateScreen from '../../../../components/general/EmptyStateScreen'
import Sidebar from './components/Sidebar'
import EmptyStateScreen from '@components/EmptyStateScreen'
import Sidebar from '@sidebar'
import ProjectList from './components/ProjectList'
import Header from './components/Header'
import SearchInput from '../../../../components/general/SearchInput'
import SearchInput from '@components/SearchInput'
export interface IProjectsKEntry {
collectionId: string

View File

@@ -1,7 +1,7 @@
import React from 'react'
import { Icon } from '@iconify/react'
import { useNavigate } from 'react-router-dom'
import GoBackButton from '../../../components/general/GoBackButton'
import GoBackButton from '@components/GoBackButton'
function Kanban(): React.ReactElement {
const navigate = useNavigate()
@@ -66,7 +66,7 @@ function Kanban(): React.ReactElement {
<Icon icon="tabler:dots-vertical" className="text-xl" />
</button>
</div>
<ul className="mt-6 flex flex-col gap-2 overflow-y-auto pr-2">
<ul className="mt-6 space-y-2 overflow-y-auto pr-2">
{Array(Math.floor(Math.random() * 10))
.fill(0)
.map((_, index) => (

View File

@@ -1,15 +1,15 @@
/* eslint-disable multiline-ternary */
/* eslint-disable @typescript-eslint/indent */
import React, { useEffect, useState } from 'react'
import ModuleHeader from '../../components/general/ModuleHeader'
import SidebarTitle from '../../components/Sidebar/components/SidebarTitle'
import SidebarDivider from '../../components/Sidebar/components/SidebarDivider'
import ModuleHeader from '@components/ModuleHeader'
import SidebarTitle from '@sidebar/components/SidebarTitle'
import SidebarDivider from '@sidebar/components/SidebarDivider'
import { Icon } from '@iconify/react'
import { Link } from 'react-router-dom'
import SidebarItem from '../../components/Sidebar/components/SidebarItem'
import SidebarItem from '@sidebar/components/SidebarItem'
import { faker } from '@faker-js/faker'
import ModuleWrapper from '../../components/general/ModuleWrapper'
import SearchInput from '../../components/general/SearchInput'
import ModuleWrapper from '@components/ModuleWrapper'
import SearchInput from '@components/SearchInput'
function shuffle(array: any[]): any[] {
let currentIndex = array.length

View File

@@ -1,12 +1,12 @@
import { faker } from '@faker-js/faker'
import { Icon } from '@iconify/react/dist/iconify.js'
import React, { useState } from 'react'
import ModuleHeader from '../../components/general/ModuleHeader'
import SidebarDivider from '../../components/Sidebar/components/SidebarDivider'
import SidebarItem from '../../components/Sidebar/components/SidebarItem'
import SidebarTitle from '../../components/Sidebar/components/SidebarTitle'
import ModuleWrapper from '../../components/general/ModuleWrapper'
import SearchInput from '../../components/general/SearchInput'
import ModuleHeader from '@components/ModuleHeader'
import SidebarDivider from '@sidebar/components/SidebarDivider'
import SidebarItem from '@sidebar/components/SidebarItem'
import SidebarTitle from '@sidebar/components/SidebarTitle'
import ModuleWrapper from '@components/ModuleWrapper'
import SearchInput from '@components/SearchInput'
function ReferenceBooks(): React.ReactElement {
const [searchQuery, setSearchQuery] = useState('')

View File

@@ -3,11 +3,11 @@
/* eslint-disable multiline-ternary */
/* eslint-disable @typescript-eslint/strict-boolean-expressions */
import React, { useEffect } from 'react'
import ModuleHeader from '../../components/general/ModuleHeader'
import ModuleHeader from '@components/ModuleHeader'
import { Icon } from '@iconify/react/dist/iconify.js'
import useFetch from '../../hooks/useFetch'
import APIComponentWithFallback from '../../components/general/APIComponentWithFallback'
import ModuleWrapper from '../../components/general/ModuleWrapper'
import useFetch from '@hooks/useFetch'
import APIComponentWithFallback from '@components/APIComponentWithFallback'
import ModuleWrapper from '@components/ModuleWrapper'
import GaugeComponent from 'react-gauge-component'
import moment from 'moment'

View File

@@ -3,8 +3,8 @@
/* eslint-disable @typescript-eslint/naming-convention */
import React, { useContext, useEffect, useState } from 'react'
import { Icon } from '@iconify/react/dist/iconify.js'
import { SpotifyContext } from '../../providers/SpotifyProvider'
import EmptyStateScreen from '../../components/general/EmptyStateScreen'
import { SpotifyContext } from '@providers/SpotifyProvider'
import EmptyStateScreen from '@components/EmptyStateScreen'
function WebPlayback(): React.ReactElement {
const { player, isPaused, isActive, currentTrack } =

View File

@@ -1,11 +1,11 @@
/* eslint-disable @typescript-eslint/strict-boolean-expressions */
/* eslint-disable multiline-ternary */
import React, { useContext } from 'react'
import ModuleHeader from '../../components/general/ModuleHeader'
import { AuthContext } from '../../providers/AuthProvider'
import ModuleHeader from '@components/ModuleHeader'
import { AuthContext } from '@providers/AuthProvider'
import { Icon } from '@iconify/react/dist/iconify.js'
import WebPlayback from './WebPlayback'
import ModuleWrapper from '../../components/general/ModuleWrapper'
import ModuleWrapper from '@components/ModuleWrapper'
function Spotify(): React.ReactElement {
const { userData } = useContext(AuthContext)

View File

@@ -4,14 +4,14 @@ import React, { useEffect, useState } from 'react'
import { Icon } from '@iconify/react/dist/iconify.js'
import { toast } from 'react-toastify'
import { useDebounce } from '@uidotdev/usehooks'
import Modal from '../../../components/general/Modal'
import ColorPickerModal from '../../../components/general/ColorPicker/ColorPickerModal'
import Modal from '@components/Modal'
import ColorPickerModal from '@components/ColorPicker/ColorPickerModal'
import { type ITodoListList } from './Sidebar'
import CreateOrModifyButton from '../../../components/general/CreateOrModifyButton'
import IconSelector from '../../../components/general/IconSelector'
import Input from '../../../components/general/Input'
import IconInput from '../../../components/general/IconSelector/IconInput'
import ColorInput from '../../../components/general/ColorPicker/ColorInput'
import CreateOrModifyButton from '@components/CreateOrModifyButton'
import IconSelector from '@components/IconSelector'
import Input from '@components/Input'
import IconInput from '@components/IconSelector/IconInput'
import ColorInput from '@components/ColorPicker/ColorInput'
import { cookieParse } from 'pocketbase'
function ModifyListModal({

View File

@@ -4,9 +4,9 @@ import React, { useEffect, useState } from 'react'
import { Icon } from '@iconify/react/dist/iconify.js'
import { toast } from 'react-toastify'
import { useDebounce } from '@uidotdev/usehooks'
import Modal from '../../../components/general/Modal'
import CreateOrModifyButton from '../../../components/general/CreateOrModifyButton'
import Input from '../../../components/general/Input'
import Modal from '@components/Modal'
import CreateOrModifyButton from '@components/CreateOrModifyButton'
import Input from '@components/Input'
import { type ITodoListTag } from './Sidebar'
import { cookieParse } from 'pocketbase'

View File

@@ -3,14 +3,14 @@
/* eslint-disable multiline-ternary */
import { Icon } from '@iconify/react/dist/iconify.js'
import React, { useEffect, useRef, useState } from 'react'
import Input from '../../../../components/general/Input'
import Input from '@components/Input'
import { type ITodoListTag, type ITodoListList } from '../Sidebar'
import { type ITodoListEntryItem } from '../..'
import HamburgerMenu from '../../../../components/general/HamburgerMenu'
import MenuItem from '../../../../components/general/HamburgerMenu/MenuItem'
import HamburgerMenu from '@components/HamburgerMenu'
import MenuItem from '@components/HamburgerMenu/MenuItem'
import { toast } from 'react-toastify'
import { cookieParse } from 'pocketbase'
import CreateOrModifyButton from '../../../../components/general/CreateOrModifyButton'
import CreateOrModifyButton from '@components/CreateOrModifyButton'
import PrioritySelector from './components/PrioritySelector'
import ListSelector from './components/ListSelector'
import TagsSelector from './components/TagsSelector'

View File

@@ -1,11 +1,11 @@
/* eslint-disable @typescript-eslint/strict-boolean-expressions */
/* eslint-disable @typescript-eslint/indent */
import React, { useEffect, useState } from 'react'
import SidebarDivider from '../../../components/Sidebar/components/SidebarDivider'
import SidebarTitle from '../../../components/Sidebar/components/SidebarTitle'
import SidebarDivider from '@sidebar/components/SidebarDivider'
import SidebarTitle from '@sidebar/components/SidebarTitle'
import { Icon } from '@iconify/react'
import APIComponentWithFallback from '../../../components/general/APIComponentWithFallback'
import GoBackButton from '../../../components/general/GoBackButton'
import APIComponentWithFallback from '@components/APIComponentWithFallback'
import GoBackButton from '@components/GoBackButton'
import ModifyListModal from './ModifyListModal'
import ModifyTagModal from './ModifyTagModal'

View File

@@ -6,14 +6,14 @@ import Sidebar, {
type ITodoListTag,
type ITodoListList
} from './components/Sidebar'
import ModuleHeader from '../../components/general/ModuleHeader'
import useFetch from '../../hooks/useFetch'
import APIComponentWithFallback from '../../components/general/APIComponentWithFallback'
import ModuleHeader from '@components/ModuleHeader'
import useFetch from '@hooks/useFetch'
import APIComponentWithFallback from '@components/APIComponentWithFallback'
import TaskItem from './components/TaskItem'
import ModifyTaskWindow from './components/ModifyTaskWindow'
import ModuleWrapper from '../../components/general/ModuleWrapper'
import SearchInput from '../../components/general/SearchInput'
import DeleteConfirmationModal from '../../components/general/DeleteConfirmationModal'
import ModuleWrapper from '@components/ModuleWrapper'
import SearchInput from '@components/SearchInput'
import DeleteConfirmationModal from '@components/DeleteConfirmationModal'
export interface ITodoListEntryItem {
collectionId: string

View File

@@ -1,12 +1,12 @@
/* eslint-disable @typescript-eslint/strict-boolean-expressions */
/* eslint-disable @typescript-eslint/member-delimiter-style */
import React, { createContext, useContext, useEffect, useState } from 'react'
import { GlobalStateContext } from './GlobalStateProvider'
import React, { createContext, useEffect, useState } from 'react'
import { AUTH_ERROR_MESSAGES } from '../constants/auth'
import { toast } from 'react-toastify'
const AUTH_DATA: {
auth: boolean
setAuth: React.Dispatch<React.SetStateAction<boolean>>
authenticate: ({
email,
password
@@ -14,7 +14,7 @@ const AUTH_DATA: {
email: string
password: string
}) => Promise<string>
authWithOauth: (provider: string) => Promise<string>
verifyToken: (token: string) => Promise<{ success: boolean; userData: any }>
logout: () => void
loginQuota: {
quota: number
@@ -26,8 +26,9 @@ const AUTH_DATA: {
getAvatarURL: () => string
} = {
auth: false,
setAuth: () => {},
authenticate: async () => '',
authWithOauth: async () => '',
verifyToken: async () => ({ success: false, userData: null }),
logout: () => {},
loginQuota: {
quota: 5,
@@ -50,9 +51,6 @@ export default function AuthProvider({
const [userData, setUserData] = useState<any>(null)
const [quota, setQuota] = useState(5)
const [authLoading, setAuthLoading] = useState(true)
const {
pocketbase: { pocketbase, loading, error }
} = useContext(GlobalStateContext)
function updateQuota(): number {
const storedQuota = window.localStorage.getItem('quota')
@@ -97,6 +95,29 @@ export default function AuthProvider({
toast.error(AUTH_ERROR_MESSAGES.QUOTA_EXCEEDED)
}
async function verifyToken(token: string): Promise<{
success: boolean
userData: any
}> {
return await fetch(`${import.meta.env.VITE_API_HOST}/user/auth/verify`, {
method: 'POST',
headers: {
Authorization: `Bearer ${token}`
}
})
.then(async res => {
const data = await res.json()
if (res.ok && data.state === 'success') {
return { success: true, userData: data.userData }
} else {
return { success: false, userData: null }
}
})
.catch(() => {
return { success: false, userData: null }
})
}
async function authenticate({
email,
password
@@ -104,109 +125,65 @@ export default function AuthProvider({
email: string
password: string
}): Promise<string> {
if (!loading && pocketbase !== null && error === null) {
try {
await pocketbase
.collection('users')
.authWithPassword(email, password)
.catch(e => {
throw e.message
})
const res = fetch(`${import.meta.env.VITE_API_HOST}/user/auth/login`, {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({ email, password })
})
.then(async res => {
const data = await res.json()
if (res.ok && data.state === 'success') {
window.localStorage.setItem('quota', '5')
window.localStorage.removeItem('lastQuotaExceeded')
window.localStorage.setItem('quota', '5')
window.localStorage.removeItem('lastQuotaExceeded')
document.cookie = `token=${data.token}; path=/; expires=${new Date(
Date.now() + 7 * 24 * 60 * 60 * 1000
).toUTCString()}`
document.cookie = `token=${
pocketbase.authStore.token
}; path=/; expires=${new Date(
Date.now() + 7 * 24 * 60 * 60 * 1000
).toUTCString()}`
setUserData(data.userData)
setAuth(true)
setUserData(pocketbase.authStore.model)
setAuth(pocketbase.authStore.isValid)
return 'success: ' + pocketbase.authStore.model?.name
} catch (error) {
switch (error) {
return 'success: ' + data.userData.name
} else {
throw new Error(data.message)
}
})
.catch(err => {
switch (err) {
case 'Failed to authenticate.':
return AUTH_ERROR_MESSAGES.INVALID_CREDENTIALS
default:
return AUTH_ERROR_MESSAGES.UNKNOWN_ERROR
}
}
} else {
return AUTH_ERROR_MESSAGES.DATABASE_NOT_READY
}
}
})
async function authWithOauth(provider: string): Promise<string> {
if (!loading && pocketbase !== null && error === null) {
try {
const w = window.open()
await pocketbase
.collection('users')
.authWithOAuth2({
provider,
urlCallback: url => {
if (w !== null) {
w.location.href = url
}
}
})
.catch(e => {
throw e.message
})
window.localStorage.setItem('quota', '5')
window.localStorage.removeItem('lastQuotaExceeded')
document.cookie = `token=${
pocketbase.authStore.token
}; path=/; expires=${new Date(
Date.now() + 7 * 24 * 60 * 60 * 1000
).toUTCString()}`
setUserData(pocketbase.authStore.model)
return 'success: ' + pocketbase.authStore.model?.name
} catch (error) {
switch (error) {
case 'Failed to authenticate.':
return AUTH_ERROR_MESSAGES.INVALID_CREDENTIALS
default:
return AUTH_ERROR_MESSAGES.UNKNOWN_ERROR
}
} finally {
setAuth(pocketbase.authStore.isValid)
}
} else {
return AUTH_ERROR_MESSAGES.DATABASE_NOT_READY
}
return await res
}
function logout(): void {
if (!loading && pocketbase !== null) {
pocketbase.authStore.clear()
setAuth(false)
document.cookie = `token=; path=/; expires=${new Date(
Date.now() + 7 * 24 * 60 * 60 * 1000
).toUTCString()}`
setUserData(null)
setAuth(false)
document.cookie = `token=; path=/; expires=${new Date(
Date.now() + 7 * 24 * 60 * 60 * 1000
).toUTCString()}`
setUserData(null)
window.localStorage.setItem('quota', '5')
window.localStorage.removeItem('lastQuotaExceeded')
}
window.localStorage.setItem('quota', '5')
window.localStorage.removeItem('lastQuotaExceeded')
}
useEffect(() => {
setAuthLoading(true)
if (!loading && pocketbase !== null && document.cookie.includes('token')) {
;(async () => {
await pocketbase.collection('users').authRefresh()
setAuth(pocketbase.authStore.isValid)
setUserData(pocketbase.authStore.model)
})()
updateQuota()
if (document.cookie.includes('token')) {
verifyToken(document.cookie.split('=')[1])
.then(async ({ success, userData }) => {
if (success) {
setUserData(userData)
setAuth(true)
}
})
.catch(() => {
setAuth(false)
})
@@ -216,18 +193,13 @@ export default function AuthProvider({
} else {
setAuthLoading(false)
}
}, [loading])
useEffect(() => {
updateQuota()
}, [])
function getAvatarURL(): string {
const _userData = pocketbase?.authStore.model
if (_userData) {
if (userData) {
return `${import.meta.env.VITE_POCKETBASE_ENDPOINT}/api/files/${
_userData.collectionId
}/${_userData.id}/${_userData.avatar}`
userData.collectionId
}/${userData.id}/${userData.avatar}`
}
return ''
}
@@ -236,8 +208,9 @@ export default function AuthProvider({
<AuthContext.Provider
value={{
auth,
setAuth,
authenticate,
authWithOauth,
verifyToken,
logout,
loginQuota: {
quota,

View File

@@ -1,23 +1,11 @@
import React, { createContext, useEffect, useState } from 'react'
import usePocketbase from '../hooks/usePocketbase'
import type Pocketbase from 'pocketbase'
const GLOBAL_STATE: {
sidebarExpanded: boolean
toggleSidebar: () => void
pocketbase: {
pocketbase: Pocketbase | null
loading: boolean
error: any
}
} = {
sidebarExpanded: true,
toggleSidebar: () => {},
pocketbase: {
pocketbase: null,
loading: false,
error: null
}
toggleSidebar: () => {}
}
export const GlobalStateContext = createContext(GLOBAL_STATE)
@@ -28,7 +16,6 @@ export default function GlobalStateProvider({
children: React.ReactNode
}): React.ReactElement {
const [navbarExpanded, setNavbarExpanded] = useState(true)
const { pocketbase, loading, error } = usePocketbase()
function toggleNavbarExpanded(): void {
setNavbarExpanded(!navbarExpanded)
@@ -44,12 +31,7 @@ export default function GlobalStateProvider({
<GlobalStateContext.Provider
value={{
sidebarExpanded: navbarExpanded,
toggleSidebar: toggleNavbarExpanded,
pocketbase: {
pocketbase,
loading,
error
}
toggleSidebar: toggleNavbarExpanded
}}
>
{children}

View File

@@ -2,7 +2,7 @@
/* eslint-disable @typescript-eslint/indent */
import moment from 'moment'
import React, { createContext, useEffect, useRef, useState } from 'react'
import useFetch from '../hooks/useFetch'
import useFetch from '@hooks/useFetch'
import { Outlet } from 'react-router'
export interface IPhotosEntryDimensionsItem {

View File

@@ -19,7 +19,14 @@
"strict": true,
"noUnusedLocals": true,
"noUnusedParameters": true,
"noFallthroughCasesInSwitch": true
"noFallthroughCasesInSwitch": true,
"paths": {
"@components/*": ["./src/components/general/*"],
"@providers/*": ["./src/providers/*"],
"@hooks/*": ["./src/hooks/*"],
"@sidebar/*": ["./src/components/Sidebar/*"]
}
},
"include": ["src"],
"references": [{ "path": "./tsconfig.node.json" }]

View File

@@ -4,7 +4,12 @@
"skipLibCheck": true,
"module": "ESNext",
"moduleResolution": "bundler",
"allowSyntheticDefaultImports": true
"allowSyntheticDefaultImports": true,
"paths": {
"@components/*": ["./src/components/general/*"],
"@providers/*": ["./src/providers/*"]
}
},
"include": ["vite.config.ts"]
}

View File

@@ -1,7 +1,20 @@
import { defineConfig } from 'vite'
import react from '@vitejs/plugin-react'
import mkcert from 'vite-plugin-mkcert'
import path from 'path'
// https://vitejs.dev/config/
export default defineConfig({
plugins: [react()],
plugins: [react(), mkcert()],
server: {
https: false
},
resolve: {
alias: {
'@components': path.resolve(__dirname, './src/components/general'),
'@providers': path.resolve(__dirname, './src/providers'),
'@hooks': path.resolve(__dirname, './src/hooks'),
'@sidebar': path.resolve(__dirname, './src/components/Sidebar')
}
}
})