This commit is contained in:
2024-10-28 15:44:34 -07:00
parent eb43280648
commit 9e55ba90e1
15 changed files with 440 additions and 153 deletions

View File

@@ -36,6 +36,18 @@ const nextConfig = {
},
];
},
images: {
deviceSizes: [640, 750, 828, 1080, 1200, 1920, 2048],
imageSizes: [16, 32, 48, 64, 96, 128, 256, 384],
formats: ['image/webp', 'image/avif'],
minimumCacheTTL: 60,
remotePatterns: [
{
protocol: 'https',
hostname: '**',
},
],
},
};
export default nextConfig;

View File

@@ -11,26 +11,27 @@
},
"dependencies": {
"@radix-ui/react-avatar": "^1.1.1",
"@radix-ui/react-dialog": "^1.1.1",
"@radix-ui/react-dialog": "^1.1.2",
"@radix-ui/react-dropdown-menu": "^2.1.2",
"@radix-ui/react-icons": "^1.3.0",
"@radix-ui/react-label": "^2.1.0",
"@radix-ui/react-popover": "^1.1.1",
"@radix-ui/react-select": "^2.1.1",
"@radix-ui/react-popover": "^1.1.2",
"@radix-ui/react-select": "^2.1.2",
"@radix-ui/react-separator": "^1.1.0",
"@radix-ui/react-slot": "^1.1.0",
"@radix-ui/react-toast": "^1.2.1",
"@radix-ui/react-tooltip": "^1.1.2",
"@react-pdf/renderer": "^3.4.0",
"@radix-ui/react-toast": "^1.2.2",
"@radix-ui/react-tooltip": "^1.1.3",
"@react-pdf/renderer": "^3.4.5",
"@t3-oss/env-nextjs": "^0.10.1",
"@vercel/analytics": "^1.3.2",
"@vercel/speed-insights": "^1.0.14",
"class-variance-authority": "^0.7.0",
"clsx": "^2.1.1",
"cn": "^0.1.1",
"fs": "0.0.1-security",
"geist": "^1.3.1",
"lucide-react": "^0.441.0",
"next": "^14.2.13",
"next": "^14.2.16",
"next-themes": "^0.3.0",
"radix-ui": "^1.0.1",
"react": "^18.3.1",
@@ -42,18 +43,18 @@
},
"devDependencies": {
"@types/eslint": "^8.56.12",
"@types/node": "^20.16.5",
"@types/react": "^18.3.7",
"@types/react-dom": "^18.3.0",
"@typescript-eslint/eslint-plugin": "^8.6.0",
"@typescript-eslint/parser": "^8.6.0",
"@types/node": "^20.17.2",
"@types/react": "^18.3.12",
"@types/react-dom": "^18.3.1",
"@typescript-eslint/eslint-plugin": "^8.12.0",
"@typescript-eslint/parser": "^8.12.0",
"eslint": "^8.57.1",
"eslint-config-next": "^14.2.12",
"eslint-config-next": "^14.2.16",
"postcss": "^8.4.47",
"prettier": "^3.3.3",
"prettier-plugin-tailwindcss": "^0.6.6",
"tailwindcss": "^3.4.12",
"typescript": "^5.6.2"
"prettier-plugin-tailwindcss": "^0.6.8",
"tailwindcss": "^3.4.14",
"typescript": "^5.6.3"
},
"ct3aMetadata": {
"initVersion": "7.37.0"

223
pnpm-lock.yaml generated
View File

@@ -12,7 +12,7 @@ importers:
specifier: ^1.1.1
version: 1.1.1(@types/react-dom@18.3.1)(@types/react@18.3.12)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
'@radix-ui/react-dialog':
specifier: ^1.1.1
specifier: ^1.1.2
version: 1.1.2(@types/react-dom@18.3.1)(@types/react@18.3.12)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
'@radix-ui/react-dropdown-menu':
specifier: ^2.1.2
@@ -24,10 +24,10 @@ importers:
specifier: ^2.1.0
version: 2.1.0(@types/react-dom@18.3.1)(@types/react@18.3.12)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
'@radix-ui/react-popover':
specifier: ^1.1.1
specifier: ^1.1.2
version: 1.1.2(@types/react-dom@18.3.1)(@types/react@18.3.12)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
'@radix-ui/react-select':
specifier: ^2.1.1
specifier: ^2.1.2
version: 2.1.2(@types/react-dom@18.3.1)(@types/react@18.3.12)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
'@radix-ui/react-separator':
specifier: ^1.1.0
@@ -36,13 +36,13 @@ importers:
specifier: ^1.1.0
version: 1.1.0(@types/react@18.3.12)(react@18.3.1)
'@radix-ui/react-toast':
specifier: ^1.2.1
specifier: ^1.2.2
version: 1.2.2(@types/react-dom@18.3.1)(@types/react@18.3.12)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
'@radix-ui/react-tooltip':
specifier: ^1.1.2
specifier: ^1.1.3
version: 1.1.3(@types/react-dom@18.3.1)(@types/react@18.3.12)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
'@react-pdf/renderer':
specifier: ^3.4.0
specifier: ^3.4.5
version: 3.4.5(react@18.3.1)
'@t3-oss/env-nextjs':
specifier: ^0.10.1
@@ -62,6 +62,9 @@ importers:
cn:
specifier: ^0.1.1
version: 0.1.1
fs:
specifier: 0.0.1-security
version: 0.0.1-security
geist:
specifier: ^1.3.1
version: 1.3.1(next@14.2.16(react-dom@18.3.1(react@18.3.1))(react@18.3.1))
@@ -69,7 +72,7 @@ importers:
specifier: ^0.441.0
version: 0.441.0(react@18.3.1)
next:
specifier: ^14.2.13
specifier: ^14.2.16
version: 14.2.16(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
next-themes:
specifier: ^0.3.0
@@ -100,25 +103,25 @@ importers:
specifier: ^8.56.12
version: 8.56.12
'@types/node':
specifier: ^20.16.5
version: 20.17.0
specifier: ^20.17.2
version: 20.17.2
'@types/react':
specifier: ^18.3.7
specifier: ^18.3.12
version: 18.3.12
'@types/react-dom':
specifier: ^18.3.0
specifier: ^18.3.1
version: 18.3.1
'@typescript-eslint/eslint-plugin':
specifier: ^8.6.0
version: 8.11.0(@typescript-eslint/parser@8.11.0(eslint@8.57.1)(typescript@5.6.3))(eslint@8.57.1)(typescript@5.6.3)
specifier: ^8.12.0
version: 8.12.0(@typescript-eslint/parser@8.12.0(eslint@8.57.1)(typescript@5.6.3))(eslint@8.57.1)(typescript@5.6.3)
'@typescript-eslint/parser':
specifier: ^8.6.0
version: 8.11.0(eslint@8.57.1)(typescript@5.6.3)
specifier: ^8.12.0
version: 8.12.0(eslint@8.57.1)(typescript@5.6.3)
eslint:
specifier: ^8.57.1
version: 8.57.1
eslint-config-next:
specifier: ^14.2.12
specifier: ^14.2.16
version: 14.2.16(eslint@8.57.1)(typescript@5.6.3)
postcss:
specifier: ^8.4.47
@@ -127,13 +130,13 @@ importers:
specifier: ^3.3.3
version: 3.3.3
prettier-plugin-tailwindcss:
specifier: ^0.6.6
specifier: ^0.6.8
version: 0.6.8(prettier@3.3.3)
tailwindcss:
specifier: ^3.4.12
specifier: ^3.4.14
version: 3.4.14
typescript:
specifier: ^5.6.2
specifier: ^5.6.3
version: 5.6.3
packages:
@@ -146,14 +149,14 @@ packages:
resolution: {integrity: sha512-FDSOghenHTiToteC/QRlv2q3DhPZ/oOXTBoirfWNx1Cx3TMVcGWQtMMmQcSvb/JjpNeGzx8Pq/b4fKEJuWm1sw==}
engines: {node: '>=6.9.0'}
'@eslint-community/eslint-utils@4.4.0':
resolution: {integrity: sha512-1/sA4dwrzBAyeUoQ6oxahHKmrZvsnLCg4RfxW3ZFGGmQkSNQPFNLV9CUEFQP1x9EYXHTo5p6xdhZM1Ne9p/AfA==}
'@eslint-community/eslint-utils@4.4.1':
resolution: {integrity: sha512-s3O3waFUrMV8P/XaF/+ZTp1X9XBZW1a4B97ZnjQF2KYWaFD2A8KyFBsrsfSjEmjn3RGWAIuvlneuZm3CUK3jbA==}
engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0}
peerDependencies:
eslint: ^6.0.0 || ^7.0.0 || >=8.0.0
'@eslint-community/regexpp@4.11.1':
resolution: {integrity: sha512-m4DVN9ZqskZoLU5GlWZadwDnYo3vAEydiUayB9widCl9ffWx2IvPnp6n3on5rJmziJSw9Bv+Z3ChDVdMwXCY8Q==}
'@eslint-community/regexpp@4.12.1':
resolution: {integrity: sha512-CCZCDJuduB9OUkFkY2IgppNZMi2lBQgD2qzwXkEia16cge2pijY/aXi96CJMquDMn3nJdlPV1A5KrJEXwfLNzQ==}
engines: {node: ^12.0.0 || ^14.0.0 || >=16.0.0}
'@eslint/eslintrc@2.1.4':
@@ -1011,8 +1014,8 @@ packages:
'@types/json5@0.0.29':
resolution: {integrity: sha512-dRLjCWHYg4oaA77cxO64oO+7JwCwnIzkZPdrrC71jQmQtlhM556pwKo5bUzqvZndkVbeFLIIi+9TC40JNF5hNQ==}
'@types/node@20.17.0':
resolution: {integrity: sha512-a7zRo0f0eLo9K5X9Wp5cAqTUNGzuFLDG2R7C4HY2BhcMAsxgSPuRvAC1ZB6QkuUQXf0YZAgfOX2ZyrBa2n4nHQ==}
'@types/node@20.17.2':
resolution: {integrity: sha512-OOHK4sjXqkL7yQ7VEEHcf6+0jSvKjWqwnaCtY7AKD/VLEvRHMsxxu7eI8ErnjxHS8VwmekD4PeVCpu4qZEZSxg==}
'@types/prop-types@15.7.13':
resolution: {integrity: sha512-hCZTSvwbzWGvhqxp/RqVqwU999pBf2vp7hzIjiYOsl8wqOmUxkQ6ddw1cV3l8811+kdUFus/q4d1Y3E3SyEifA==}
@@ -1023,8 +1026,8 @@ packages:
'@types/react@18.3.12':
resolution: {integrity: sha512-D2wOSq/d6Agt28q7rSI3jhU7G6aiuzljDGZ2hTZHIkrTLUI+AF3WMeKkEZ9nN2fkBAlcktT6vcZjDFiIhMYEQw==}
'@typescript-eslint/eslint-plugin@8.11.0':
resolution: {integrity: sha512-KhGn2LjW1PJT2A/GfDpiyOfS4a8xHQv2myUagTM5+zsormOmBlYsnQ6pobJ8XxJmh6hnHwa2Mbe3fPrDJoDhbA==}
'@typescript-eslint/eslint-plugin@8.12.0':
resolution: {integrity: sha512-uRqchEKT0/OwDePTwCjSFO2aH4zccdeQ7DgAzM/8fuXc+PAXvpdMRbuo+oCmK1lSfXssk2UUBNiWihobKxQp/g==}
engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
peerDependencies:
'@typescript-eslint/parser': ^8.0.0 || ^8.0.0-alpha.0
@@ -1034,8 +1037,8 @@ packages:
typescript:
optional: true
'@typescript-eslint/parser@8.11.0':
resolution: {integrity: sha512-lmt73NeHdy1Q/2ul295Qy3uninSqi6wQI18XwSpm8w0ZbQXUpjCAWP1Vlv/obudoBiIjJVjlztjQ+d/Md98Yxg==}
'@typescript-eslint/parser@8.12.0':
resolution: {integrity: sha512-7U20duDQWAOhCk2VtyY41Vor/CJjiEW063Zel9aoRXq89FQ/jr+0e0m3kxh9Sk5SFW9B1AblVIBtXd+1xQ1NWQ==}
engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
peerDependencies:
eslint: ^8.57.0 || ^9.0.0
@@ -1044,12 +1047,12 @@ packages:
typescript:
optional: true
'@typescript-eslint/scope-manager@8.11.0':
resolution: {integrity: sha512-Uholz7tWhXmA4r6epo+vaeV7yjdKy5QFCERMjs1kMVsLRKIrSdM6o21W2He9ftp5PP6aWOVpD5zvrvuHZC0bMQ==}
'@typescript-eslint/scope-manager@8.12.0':
resolution: {integrity: sha512-jbuCXK18iEshRFUtlCIMAmOKA6OAsKjo41UcXPqx7ZWh2b4cmg6pV/pNcZSB7oW9mtgF95yizr7Jnwt3IUD2pA==}
engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
'@typescript-eslint/type-utils@8.11.0':
resolution: {integrity: sha512-ItiMfJS6pQU0NIKAaybBKkuVzo6IdnAhPFZA/2Mba/uBjuPQPet/8+zh5GtLHwmuFRShZx+8lhIs7/QeDHflOg==}
'@typescript-eslint/type-utils@8.12.0':
resolution: {integrity: sha512-cHioAZO/nLgyzTmwv7gWIjEKMHSbioKEZqLCaItTn7RvJP1QipuGVwEjPJa6Kv9u9UiUMVAESY9JH186TjKITw==}
engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
peerDependencies:
typescript: '*'
@@ -1057,12 +1060,12 @@ packages:
typescript:
optional: true
'@typescript-eslint/types@8.11.0':
resolution: {integrity: sha512-tn6sNMHf6EBAYMvmPUaKaVeYvhUsrE6x+bXQTxjQRp360h1giATU0WvgeEys1spbvb5R+VpNOZ+XJmjD8wOUHw==}
'@typescript-eslint/types@8.12.0':
resolution: {integrity: sha512-Cc+iNtqBJ492f8KLEmKXe1l6683P0MlFO8Bk1NMphnzVIGH4/Wn9kvandFH+gYR1DDUjH/hgeWRGdO5Tj8gjYg==}
engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
'@typescript-eslint/typescript-estree@8.11.0':
resolution: {integrity: sha512-yHC3s1z1RCHoCz5t06gf7jH24rr3vns08XXhfEqzYpd6Hll3z/3g23JRi0jM8A47UFKNc3u/y5KIMx8Ynbjohg==}
'@typescript-eslint/typescript-estree@8.12.0':
resolution: {integrity: sha512-a4koVV7HHVOQWcGb6ZcAlunJnAdwo/CITRbleQBSjq5+2WLoAJQCAAiecvrAdSM+n/man6Ghig5YgdGVIC6xqw==}
engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
peerDependencies:
typescript: '*'
@@ -1070,14 +1073,14 @@ packages:
typescript:
optional: true
'@typescript-eslint/utils@8.11.0':
resolution: {integrity: sha512-CYiX6WZcbXNJV7UNB4PLDIBtSdRmRI/nb0FMyqHPTQD1rMjA0foPLaPUV39C/MxkTd/QKSeX+Gb34PPsDVC35g==}
'@typescript-eslint/utils@8.12.0':
resolution: {integrity: sha512-5i1tqLwlf0fpX1j05paNKyIzla/a4Y3Xhh6AFzi0do/LDJLvohtZYaisaTB9kq0D4uBocAxWDTGzNMOCCwIgXA==}
engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
peerDependencies:
eslint: ^8.57.0 || ^9.0.0
'@typescript-eslint/visitor-keys@8.11.0':
resolution: {integrity: sha512-EaewX6lxSjRJnc+99+dqzTeoDZUfyrA52d2/HRrkI830kgovWsmIiTfmr0NZorzqic7ga+1bS60lRBUgR3n/Bw==}
'@typescript-eslint/visitor-keys@8.12.0':
resolution: {integrity: sha512-2rXkr+AtZZLuNY18aUjv5wtB9oUiwY1WnNi7VTsdCdy1m958ULeUKoAegldQTjqpbpNJ5cQ4egR8/bh5tbrKKQ==}
engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
'@ungap/structured-clone@1.2.0':
@@ -1125,8 +1128,8 @@ packages:
peerDependencies:
acorn: ^6.0.0 || ^7.0.0 || ^8.0.0
acorn@8.13.0:
resolution: {integrity: sha512-8zSiw54Oxrdym50NlZ9sUusyO1Z1ZchgRLWRaK6c86XJFClyCgFKetdowBg5bKxyp/u+CDBJG4Mpp0m3HLZl9w==}
acorn@8.14.0:
resolution: {integrity: sha512-cl669nCJTZBsL97OF4kUQm5g5hC2uihk0NxY3WENAC0TYdILVkAyHymAntgxGkl7K+t0cXIrH5siy5S4XkFycA==}
engines: {node: '>=0.4.0'}
hasBin: true
@@ -1223,8 +1226,8 @@ packages:
aws-sign@0.3.0:
resolution: {integrity: sha512-pEMJAknifcXqXqYVXzGPIu8mJvxtJxIdpVpAs8HNS+paT+9srRUDMQn+3hULS7WbLmttcmvgMvnDcFujqXJyPw==}
axe-core@4.10.1:
resolution: {integrity: sha512-qPC9o+kD8Tir0lzNGLeghbOrWMr3ZJpaRlCIb6Uobt/7N4FiEDvqUMnxzCHRHmg8vOg14kr5gVNyScRmbMaJ9g==}
axe-core@4.10.2:
resolution: {integrity: sha512-RE3mdQ7P3FRSe7eqCWoeQ/Z9QXrtniSjp1wUjt5nRC3WIpz5rSCve6o3fsZ2aCpJtrZjSZgjwXAoTO5k4tEI0w==}
engines: {node: '>=4'}
axobject-query@4.1.0:
@@ -1281,8 +1284,8 @@ packages:
resolution: {integrity: sha512-QOSvevhslijgYwRx6Rv7zKdMF8lbRmx+uQGx2+vDc+KI/eBnsy9kit5aj23AgGu3pa4t9AgwbnXWqS+iOY+2aA==}
engines: {node: '>= 6'}
caniuse-lite@1.0.30001669:
resolution: {integrity: sha512-DlWzFDJqstqtIVx1zeSpIMLjunf5SmwOw0N2Ck/QSQdS8PLS4+9HrLaYei4w8BIAL7IB/UEDu889d8vhCTPA0w==}
caniuse-lite@1.0.30001673:
resolution: {integrity: sha512-WTrjUCSMp3LYX0nE12ECkV0a+e6LC85E0Auz75555/qr78Oc8YWhEPNfDd6SHdtlCMSzqtuXY0uyEMNRcsKpKw==}
chalk@4.1.2:
resolution: {integrity: sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==}
@@ -1539,8 +1542,8 @@ packages:
'@typescript-eslint/parser':
optional: true
eslint-plugin-jsx-a11y@6.10.1:
resolution: {integrity: sha512-zHByM9WTUMnfsDTafGXRiqxp6lFtNoSOWBY6FonVRn3A+BUwN1L/tdBXT40BcBJi0cZjOGTXZ0eD/rTG9fEJ0g==}
eslint-plugin-jsx-a11y@6.10.2:
resolution: {integrity: sha512-scB3nz4WmG75pV8+3eRUQOHZlNSUhFNq37xnpgRkCCELU3XMvXAxLk1eqWWyE22Ki4Q01Fnsw9BA3cJHDPgn2Q==}
engines: {node: '>=4.0'}
peerDependencies:
eslint: ^3 || ^4 || ^5 || ^6 || ^7 || ^8 || ^9
@@ -1650,6 +1653,9 @@ packages:
fs.realpath@1.0.0:
resolution: {integrity: sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==}
fs@0.0.1-security:
resolution: {integrity: sha512-3XY9e1pP0CVEUCdj5BmfIZxRBTSDycnbqhIOGec9QYtmVH2fbLpj86CFWkrNOkt/Fvty4KZG5lTglL9j/gJ87w==}
fsevents@2.3.3:
resolution: {integrity: sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==}
engines: {node: ^8.16.0 || ^10.6.0 || >=11.0.0}
@@ -2789,12 +2795,12 @@ snapshots:
dependencies:
regenerator-runtime: 0.14.1
'@eslint-community/eslint-utils@4.4.0(eslint@8.57.1)':
'@eslint-community/eslint-utils@4.4.1(eslint@8.57.1)':
dependencies:
eslint: 8.57.1
eslint-visitor-keys: 3.4.3
'@eslint-community/regexpp@4.11.1': {}
'@eslint-community/regexpp@4.12.1': {}
'@eslint/eslintrc@2.1.4':
dependencies:
@@ -3736,7 +3742,7 @@ snapshots:
'@types/json5@0.0.29': {}
'@types/node@20.17.0':
'@types/node@20.17.2':
dependencies:
undici-types: 6.19.8
@@ -3751,14 +3757,14 @@ snapshots:
'@types/prop-types': 15.7.13
csstype: 3.1.3
'@typescript-eslint/eslint-plugin@8.11.0(@typescript-eslint/parser@8.11.0(eslint@8.57.1)(typescript@5.6.3))(eslint@8.57.1)(typescript@5.6.3)':
'@typescript-eslint/eslint-plugin@8.12.0(@typescript-eslint/parser@8.12.0(eslint@8.57.1)(typescript@5.6.3))(eslint@8.57.1)(typescript@5.6.3)':
dependencies:
'@eslint-community/regexpp': 4.11.1
'@typescript-eslint/parser': 8.11.0(eslint@8.57.1)(typescript@5.6.3)
'@typescript-eslint/scope-manager': 8.11.0
'@typescript-eslint/type-utils': 8.11.0(eslint@8.57.1)(typescript@5.6.3)
'@typescript-eslint/utils': 8.11.0(eslint@8.57.1)(typescript@5.6.3)
'@typescript-eslint/visitor-keys': 8.11.0
'@eslint-community/regexpp': 4.12.1
'@typescript-eslint/parser': 8.12.0(eslint@8.57.1)(typescript@5.6.3)
'@typescript-eslint/scope-manager': 8.12.0
'@typescript-eslint/type-utils': 8.12.0(eslint@8.57.1)(typescript@5.6.3)
'@typescript-eslint/utils': 8.12.0(eslint@8.57.1)(typescript@5.6.3)
'@typescript-eslint/visitor-keys': 8.12.0
eslint: 8.57.1
graphemer: 1.4.0
ignore: 5.3.2
@@ -3769,12 +3775,12 @@ snapshots:
transitivePeerDependencies:
- supports-color
'@typescript-eslint/parser@8.11.0(eslint@8.57.1)(typescript@5.6.3)':
'@typescript-eslint/parser@8.12.0(eslint@8.57.1)(typescript@5.6.3)':
dependencies:
'@typescript-eslint/scope-manager': 8.11.0
'@typescript-eslint/types': 8.11.0
'@typescript-eslint/typescript-estree': 8.11.0(typescript@5.6.3)
'@typescript-eslint/visitor-keys': 8.11.0
'@typescript-eslint/scope-manager': 8.12.0
'@typescript-eslint/types': 8.12.0
'@typescript-eslint/typescript-estree': 8.12.0(typescript@5.6.3)
'@typescript-eslint/visitor-keys': 8.12.0
debug: 4.3.7
eslint: 8.57.1
optionalDependencies:
@@ -3782,15 +3788,15 @@ snapshots:
transitivePeerDependencies:
- supports-color
'@typescript-eslint/scope-manager@8.11.0':
'@typescript-eslint/scope-manager@8.12.0':
dependencies:
'@typescript-eslint/types': 8.11.0
'@typescript-eslint/visitor-keys': 8.11.0
'@typescript-eslint/types': 8.12.0
'@typescript-eslint/visitor-keys': 8.12.0
'@typescript-eslint/type-utils@8.11.0(eslint@8.57.1)(typescript@5.6.3)':
'@typescript-eslint/type-utils@8.12.0(eslint@8.57.1)(typescript@5.6.3)':
dependencies:
'@typescript-eslint/typescript-estree': 8.11.0(typescript@5.6.3)
'@typescript-eslint/utils': 8.11.0(eslint@8.57.1)(typescript@5.6.3)
'@typescript-eslint/typescript-estree': 8.12.0(typescript@5.6.3)
'@typescript-eslint/utils': 8.12.0(eslint@8.57.1)(typescript@5.6.3)
debug: 4.3.7
ts-api-utils: 1.3.0(typescript@5.6.3)
optionalDependencies:
@@ -3799,12 +3805,12 @@ snapshots:
- eslint
- supports-color
'@typescript-eslint/types@8.11.0': {}
'@typescript-eslint/types@8.12.0': {}
'@typescript-eslint/typescript-estree@8.11.0(typescript@5.6.3)':
'@typescript-eslint/typescript-estree@8.12.0(typescript@5.6.3)':
dependencies:
'@typescript-eslint/types': 8.11.0
'@typescript-eslint/visitor-keys': 8.11.0
'@typescript-eslint/types': 8.12.0
'@typescript-eslint/visitor-keys': 8.12.0
debug: 4.3.7
fast-glob: 3.3.2
is-glob: 4.0.3
@@ -3816,20 +3822,20 @@ snapshots:
transitivePeerDependencies:
- supports-color
'@typescript-eslint/utils@8.11.0(eslint@8.57.1)(typescript@5.6.3)':
'@typescript-eslint/utils@8.12.0(eslint@8.57.1)(typescript@5.6.3)':
dependencies:
'@eslint-community/eslint-utils': 4.4.0(eslint@8.57.1)
'@typescript-eslint/scope-manager': 8.11.0
'@typescript-eslint/types': 8.11.0
'@typescript-eslint/typescript-estree': 8.11.0(typescript@5.6.3)
'@eslint-community/eslint-utils': 4.4.1(eslint@8.57.1)
'@typescript-eslint/scope-manager': 8.12.0
'@typescript-eslint/types': 8.12.0
'@typescript-eslint/typescript-estree': 8.12.0(typescript@5.6.3)
eslint: 8.57.1
transitivePeerDependencies:
- supports-color
- typescript
'@typescript-eslint/visitor-keys@8.11.0':
'@typescript-eslint/visitor-keys@8.12.0':
dependencies:
'@typescript-eslint/types': 8.11.0
'@typescript-eslint/types': 8.12.0
eslint-visitor-keys: 3.4.3
'@ungap/structured-clone@1.2.0': {}
@@ -3848,11 +3854,11 @@ snapshots:
abs-svg-path@0.1.1: {}
acorn-jsx@5.3.2(acorn@8.13.0):
acorn-jsx@5.3.2(acorn@8.14.0):
dependencies:
acorn: 8.13.0
acorn: 8.14.0
acorn@8.13.0: {}
acorn@8.14.0: {}
ajv@6.12.6:
dependencies:
@@ -3967,7 +3973,7 @@ snapshots:
aws-sign@0.3.0: {}
axe-core@4.10.1: {}
axe-core@4.10.2: {}
axobject-query@4.1.0: {}
@@ -4022,7 +4028,7 @@ snapshots:
camelcase-css@2.0.1: {}
caniuse-lite@1.0.30001669: {}
caniuse-lite@1.0.30001673: {}
chalk@4.1.2:
dependencies:
@@ -4274,13 +4280,13 @@ snapshots:
dependencies:
'@next/eslint-plugin-next': 14.2.16
'@rushstack/eslint-patch': 1.10.4
'@typescript-eslint/eslint-plugin': 8.11.0(@typescript-eslint/parser@8.11.0(eslint@8.57.1)(typescript@5.6.3))(eslint@8.57.1)(typescript@5.6.3)
'@typescript-eslint/parser': 8.11.0(eslint@8.57.1)(typescript@5.6.3)
'@typescript-eslint/eslint-plugin': 8.12.0(@typescript-eslint/parser@8.12.0(eslint@8.57.1)(typescript@5.6.3))(eslint@8.57.1)(typescript@5.6.3)
'@typescript-eslint/parser': 8.12.0(eslint@8.57.1)(typescript@5.6.3)
eslint: 8.57.1
eslint-import-resolver-node: 0.3.9
eslint-import-resolver-typescript: 3.6.3(@typescript-eslint/parser@8.11.0(eslint@8.57.1)(typescript@5.6.3))(eslint-import-resolver-node@0.3.9)(eslint-plugin-import@2.31.0)(eslint@8.57.1)
eslint-plugin-import: 2.31.0(@typescript-eslint/parser@8.11.0(eslint@8.57.1)(typescript@5.6.3))(eslint-import-resolver-typescript@3.6.3)(eslint@8.57.1)
eslint-plugin-jsx-a11y: 6.10.1(eslint@8.57.1)
eslint-import-resolver-typescript: 3.6.3(@typescript-eslint/parser@8.12.0(eslint@8.57.1)(typescript@5.6.3))(eslint-import-resolver-node@0.3.9)(eslint-plugin-import@2.31.0)(eslint@8.57.1)
eslint-plugin-import: 2.31.0(@typescript-eslint/parser@8.12.0(eslint@8.57.1)(typescript@5.6.3))(eslint-import-resolver-typescript@3.6.3)(eslint@8.57.1)
eslint-plugin-jsx-a11y: 6.10.2(eslint@8.57.1)
eslint-plugin-react: 7.37.2(eslint@8.57.1)
eslint-plugin-react-hooks: 5.0.0-canary-7118f5dd7-20230705(eslint@8.57.1)
optionalDependencies:
@@ -4298,37 +4304,37 @@ snapshots:
transitivePeerDependencies:
- supports-color
eslint-import-resolver-typescript@3.6.3(@typescript-eslint/parser@8.11.0(eslint@8.57.1)(typescript@5.6.3))(eslint-import-resolver-node@0.3.9)(eslint-plugin-import@2.31.0)(eslint@8.57.1):
eslint-import-resolver-typescript@3.6.3(@typescript-eslint/parser@8.12.0(eslint@8.57.1)(typescript@5.6.3))(eslint-import-resolver-node@0.3.9)(eslint-plugin-import@2.31.0)(eslint@8.57.1):
dependencies:
'@nolyfill/is-core-module': 1.0.39
debug: 4.3.7
enhanced-resolve: 5.17.1
eslint: 8.57.1
eslint-module-utils: 2.12.0(@typescript-eslint/parser@8.11.0(eslint@8.57.1)(typescript@5.6.3))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.6.3)(eslint@8.57.1)
eslint-module-utils: 2.12.0(@typescript-eslint/parser@8.12.0(eslint@8.57.1)(typescript@5.6.3))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.6.3)(eslint@8.57.1)
fast-glob: 3.3.2
get-tsconfig: 4.8.1
is-bun-module: 1.2.1
is-glob: 4.0.3
optionalDependencies:
eslint-plugin-import: 2.31.0(@typescript-eslint/parser@8.11.0(eslint@8.57.1)(typescript@5.6.3))(eslint-import-resolver-typescript@3.6.3)(eslint@8.57.1)
eslint-plugin-import: 2.31.0(@typescript-eslint/parser@8.12.0(eslint@8.57.1)(typescript@5.6.3))(eslint-import-resolver-typescript@3.6.3)(eslint@8.57.1)
transitivePeerDependencies:
- '@typescript-eslint/parser'
- eslint-import-resolver-node
- eslint-import-resolver-webpack
- supports-color
eslint-module-utils@2.12.0(@typescript-eslint/parser@8.11.0(eslint@8.57.1)(typescript@5.6.3))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.6.3)(eslint@8.57.1):
eslint-module-utils@2.12.0(@typescript-eslint/parser@8.12.0(eslint@8.57.1)(typescript@5.6.3))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.6.3)(eslint@8.57.1):
dependencies:
debug: 3.2.7
optionalDependencies:
'@typescript-eslint/parser': 8.11.0(eslint@8.57.1)(typescript@5.6.3)
'@typescript-eslint/parser': 8.12.0(eslint@8.57.1)(typescript@5.6.3)
eslint: 8.57.1
eslint-import-resolver-node: 0.3.9
eslint-import-resolver-typescript: 3.6.3(@typescript-eslint/parser@8.11.0(eslint@8.57.1)(typescript@5.6.3))(eslint-import-resolver-node@0.3.9)(eslint-plugin-import@2.31.0)(eslint@8.57.1)
eslint-import-resolver-typescript: 3.6.3(@typescript-eslint/parser@8.12.0(eslint@8.57.1)(typescript@5.6.3))(eslint-import-resolver-node@0.3.9)(eslint-plugin-import@2.31.0)(eslint@8.57.1)
transitivePeerDependencies:
- supports-color
eslint-plugin-import@2.31.0(@typescript-eslint/parser@8.11.0(eslint@8.57.1)(typescript@5.6.3))(eslint-import-resolver-typescript@3.6.3)(eslint@8.57.1):
eslint-plugin-import@2.31.0(@typescript-eslint/parser@8.12.0(eslint@8.57.1)(typescript@5.6.3))(eslint-import-resolver-typescript@3.6.3)(eslint@8.57.1):
dependencies:
'@rtsao/scc': 1.1.0
array-includes: 3.1.8
@@ -4339,7 +4345,7 @@ snapshots:
doctrine: 2.1.0
eslint: 8.57.1
eslint-import-resolver-node: 0.3.9
eslint-module-utils: 2.12.0(@typescript-eslint/parser@8.11.0(eslint@8.57.1)(typescript@5.6.3))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.6.3)(eslint@8.57.1)
eslint-module-utils: 2.12.0(@typescript-eslint/parser@8.12.0(eslint@8.57.1)(typescript@5.6.3))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.6.3)(eslint@8.57.1)
hasown: 2.0.2
is-core-module: 2.15.1
is-glob: 4.0.3
@@ -4351,23 +4357,22 @@ snapshots:
string.prototype.trimend: 1.0.8
tsconfig-paths: 3.15.0
optionalDependencies:
'@typescript-eslint/parser': 8.11.0(eslint@8.57.1)(typescript@5.6.3)
'@typescript-eslint/parser': 8.12.0(eslint@8.57.1)(typescript@5.6.3)
transitivePeerDependencies:
- eslint-import-resolver-typescript
- eslint-import-resolver-webpack
- supports-color
eslint-plugin-jsx-a11y@6.10.1(eslint@8.57.1):
eslint-plugin-jsx-a11y@6.10.2(eslint@8.57.1):
dependencies:
aria-query: 5.3.2
array-includes: 3.1.8
array.prototype.flatmap: 1.3.2
ast-types-flow: 0.0.8
axe-core: 4.10.1
axe-core: 4.10.2
axobject-query: 4.1.0
damerau-levenshtein: 1.0.8
emoji-regex: 9.2.2
es-iterator-helpers: 1.1.0
eslint: 8.57.1
hasown: 2.0.2
jsx-ast-utils: 3.3.5
@@ -4412,8 +4417,8 @@ snapshots:
eslint@8.57.1:
dependencies:
'@eslint-community/eslint-utils': 4.4.0(eslint@8.57.1)
'@eslint-community/regexpp': 4.11.1
'@eslint-community/eslint-utils': 4.4.1(eslint@8.57.1)
'@eslint-community/regexpp': 4.12.1
'@eslint/eslintrc': 2.1.4
'@eslint/js': 8.57.1
'@humanwhocodes/config-array': 0.13.0
@@ -4455,8 +4460,8 @@ snapshots:
espree@9.6.1:
dependencies:
acorn: 8.13.0
acorn-jsx: 5.3.2(acorn@8.13.0)
acorn: 8.14.0
acorn-jsx: 5.3.2(acorn@8.14.0)
eslint-visitor-keys: 3.4.3
esquery@1.6.0:
@@ -4543,6 +4548,8 @@ snapshots:
fs.realpath@1.0.0: {}
fs@0.0.1-security: {}
fsevents@2.3.3:
optional: true
@@ -4946,7 +4953,7 @@ snapshots:
'@next/env': 14.2.16
'@swc/helpers': 0.5.5
busboy: 1.6.0
caniuse-lite: 1.0.30001669
caniuse-lite: 1.0.30001673
graceful-fs: 4.2.11
postcss: 8.4.31
react: 18.3.1

12
public/publications.bib Normal file
View File

@@ -0,0 +1,12 @@
@inproceedings{OConnor2024,
abstract = {Human-robot interaction (HRI) research plays a pivotal role in shaping how robots communicate and collaborate with humans. However, conducting HRI studies, particularly those employing the Wizard-of-Oz (WoZ) technique, can be challenging. WoZ user studies can have complexities at the technical and methodological levels that may render the results irreproducible. We propose to address these challenges with HRIStudio, a novel web-based platform designed to streamline the design, execution, and analysis of WoZ experiments. HRIStudio offers an intuitive interface for experiment creation, real-time control and monitoring during experimental runs, and comprehensive data logging and playback tools for analysis and reproducibility. By lowering technical barriers, promoting collaboration, and offering methodological guidelines, HRIStudio aims to make human-centered robotics research easier, and at the same time, empower researchers to develop scientifically rigorous user studies.},
author = {O'Connor, Sean and Perrone, L. Felipe},
title = {HRIStudio: A Framework for Wizard-of-Oz Experiments in Human-Robot Interaction Studies (Late Breaking Report)},
booktitle={33rd IEEE International Conference on Robot and Human Interactive Communication (RO-MAN)},
year = {2024}
url = {https://soconnor.dev/publications/hristudio-lbr.pdf},
paperUrl = {/publications/hristudio-lbr.pdf},
posterUrl = {/publications/hristudio-lbr-poster.pdf}
}

Binary file not shown.

Binary file not shown.

View File

@@ -2,25 +2,34 @@
export default function CVPage() {
return (
<div className="bg-white shadow-sm rounded-lg overflow-hidden">
<object
data="/cv.pdf"
type="application/pdf"
className="w-full h-[calc(100vh-11rem)]"
>
<div className="flex flex-col items-center justify-center p-8">
<p className="text-lg text-muted-foreground">
Your browser doesn't support PDF preview.
</p>
<a
href="/cv.pdf"
download
className="mt-4 inline-flex items-center justify-center px-4 pt-2 pb-0 border border-transparent text-sm font-medium rounded-md text-white bg-primary hover:bg-primary/90 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-primary"
>
Download PDF
</a>
</div>
</object>
<div className="space-y-8">
<section className="prose prose-zinc dark:prose-invert max-w-none">
<h1 className="text-2xl font-bold">Curriculum Vitae</h1>
<p className="text-lg text-muted-foreground">
My academic and professional experience in computer science, robotics, and engineering.
</p>
</section>
<div className="bg-white shadow-sm rounded-lg overflow-hidden">
<object
data="/cv.pdf"
type="application/pdf"
className="w-full h-[calc(100vh-18rem)] lg:h-[calc(100vh-15rem)]"
>
<div className="flex flex-col items-center justify-center p-8">
<p className="text-lg text-muted-foreground">
Your browser doesn't support PDF preview.
</p>
<a
href="/cv.pdf"
download
className="mt-4 inline-flex items-center justify-center px-4 pt-2 pb-0 border border-transparent text-sm font-medium rounded-md text-white bg-primary hover:bg-primary/90 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-primary"
>
Download PDF
</a>
</div>
</object>
</div>
</div>
);
}

View File

@@ -10,9 +10,9 @@ export default function HomePage() {
{/* About Section */}
<section className="space-y-6">
<div>
<h1 className="text-2xl font-bold">About Me</h1>
<h1 className="text-2xl font-bold">Hi! I'm Sean.</h1>
<p className="text-lg text-muted-foreground mt-2">
I'm a Computer Science and Engineering student at Bucknell University, passionate about robotics,
I am a Computer Science and Engineering student at Bucknell University, passionate about robotics,
software development, and human-computer interaction. With a strong foundation in both academic
research and practical development, I bridge the gap between theoretical concepts and real-world applications.
</p>

View File

@@ -57,8 +57,10 @@ export default function ProjectsPage() {
<Image
src={project.image}
alt={project.title}
fill
className="object-contain"
width={400}
height={300}
className="object-contain w-full h-full"
priority={index === 0}
/>
</div>
</div>

View File

@@ -0,0 +1,124 @@
'use client';
import { Card, CardHeader, CardTitle, CardDescription, CardContent } from "~/components/ui/card";
import { Badge } from "~/components/ui/badge";
import { ArrowUpRight, FileText, Presentation } from "lucide-react";
import Link from "next/link";
import { parseBibtex } from "~/lib/bibtex";
import { useEffect, useState } from "react";
import type { Publication } from "~/lib/bibtex";
import { Skeleton } from "~/components/ui/skeleton";
export default function PublicationsPage() {
const [publications, setPublications] = useState<Publication[]>([]);
const [loading, setLoading] = useState(true);
useEffect(() => {
fetch('/publications.bib')
.then(res => res.text())
.then(text => {
const pubs = parseBibtex(text);
setPublications(pubs);
setLoading(false);
});
}, []);
return (
<div className="space-y-8">
<section className="prose prose-zinc dark:prose-invert max-w-none">
<h1 className="text-2xl font-bold">Publications</h1>
<p className="text-lg text-muted-foreground">
My research publications in human-robot interaction and robotics.
</p>
</section>
<div className="space-y-6">
{loading ? (
<Card>
<CardHeader className="pb-2">
<Skeleton className="h-6 w-1/2" />
</CardHeader>
<CardContent>
<Skeleton className="h-4 w-full" />
<Skeleton className="h-4 w-full mt-2" />
</CardContent>
</Card>
) : (
publications.map((pub, index) => (
<Card key={index}>
<CardHeader className="pb-2">
<div className="flex items-center justify-between">
<CardTitle>{pub.title}</CardTitle>
{pub.url && (
<Link
href={pub.url}
target="_blank"
rel="noopener noreferrer"
className="text-muted-foreground hover:text-primary"
>
<ArrowUpRight className="h-5 w-5" />
</Link>
)}
</div>
<CardDescription className="text-base">
{pub.authors.join(', ')}
</CardDescription>
<CardDescription className="text-sm">
{pub.venue} ({pub.year})
</CardDescription>
</CardHeader>
<CardContent className="pt-0">
{pub.abstract && (
<p className="text-sm text-muted-foreground">
{pub.abstract}
</p>
)}
<div className="flex flex-wrap gap-2 mt-4">
<Badge variant="secondary" className="capitalize">
{pub.type}
</Badge>
{pub.doi && (
<Link
href={`https://doi.org/${pub.doi}`}
target="_blank"
rel="noopener noreferrer"
>
<Badge variant="outline" className="capitalize">
<ArrowUpRight className="h-4 w-4" />
DOI
</Badge>
</Link>
)}
{pub.paperUrl && (
<Link
href={pub.paperUrl}
target="_blank"
rel="noopener noreferrer"
>
<Badge variant="outline" className="capitalize">
<FileText className="h-4 w-4" />
Paper
</Badge>
</Link>
)}
{pub.posterUrl && (
<Link
href={pub.posterUrl}
target="_blank"
rel="noopener noreferrer"
>
<Badge variant="outline" className="capitalize">
<Presentation className="h-4 w-4" />
Poster
</Badge>
</Link>
)}
</div>
</CardContent>
</Card>
))
)}
</div>
</div>
);
}

View File

@@ -4,12 +4,13 @@ import Link from 'next/link';
import { usePathname } from 'next/navigation';
import { useTheme } from 'next-themes';
import { useState } from 'react';
import { Home, FolderGit2, FileText, Menu, X } from 'lucide-react';
import { Home, FolderGit2, FileText, BookOpenText, Menu, X } from 'lucide-react';
// Define the nav items without icons
const navItems = [
{ href: '/', label: 'About', icon: Home },
{ href: '/projects', label: 'Projects', icon: FolderGit2 },
{ href: '/publications', label: 'Publications', icon: BookOpenText },
{ href: '/cv', label: 'CV', icon: FileText },
];

View File

@@ -65,8 +65,9 @@ export function Sidebar() {
<Image
src="/headshot.png"
alt="Sean O'Connor"
fill
className="object-cover"
width={240}
height={240}
className="object-cover rounded-xl"
priority
/>
</div>
@@ -119,9 +120,12 @@ export function Sidebar() {
<Image
src="/headshot.png"
alt="Sean O'Connor"
fill
className="object-cover"
width={240}
height={240}
className="object-cover rounded-xl"
priority
placeholder="blur"
blurDataURL="data:image/jpeg;base64,/9j..."
/>
</div>
</div>

View File

@@ -4,7 +4,7 @@ import { cva, type VariantProps } from "class-variance-authority"
import { cn } from "~/lib/utils"
const badgeVariants = cva(
"inline-flex items-center rounded-md border px-2.5 py-0.5 text-xs font-semibold transition-colors focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2",
"inline-flex items-center gap-1.5 rounded-md border px-2 py-1 text-xs font-semibold transition-colors focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2",
{
variants: {
variant: {

View File

@@ -0,0 +1,15 @@
import { cn } from "~/lib/utils"
function Skeleton({
className,
...props
}: React.HTMLAttributes<HTMLDivElement>) {
return (
<div
className={cn("animate-pulse rounded-md bg-primary/10", className)}
{...props}
/>
)
}
export { Skeleton }

100
src/lib/bibtex.ts Normal file
View File

@@ -0,0 +1,100 @@
export type Publication = {
title: string;
authors: string[];
venue: string;
year: number;
doi?: string;
url?: string;
paperUrl?: string;
posterUrl?: string;
abstract?: string;
type: 'conference' | 'journal' | 'workshop' | 'thesis';
};
type BibTeXEntry = {
type: string;
fields: Record<string, string>;
};
function parseAuthors(authorString: string): string[] {
return authorString
.split(' and ')
.map(author => author.trim())
.map(author => {
if (author.includes(',')) {
const [lastName, firstName] = author.split(',').map(s => s.trim());
return `${firstName} ${lastName}`;
}
return author;
});
}
function parseBibTeXEntry(entry: string): BibTeXEntry | null {
// Match the entry type and content
const typeMatch = entry.match(/^(\w+)\s*{\s*[\w\d-_]+\s*,/);
if (!typeMatch) return null;
const type = typeMatch[1]!.toLowerCase();
const content = entry.slice(typeMatch[0].length);
const fields: Record<string, string> = {};
let currentField = '';
let buffer = '';
// Split into lines and process each line
const lines = content.split('\n');
for (const line of lines) {
const trimmedLine = line.trim();
if (!trimmedLine || trimmedLine === '}') continue;
// Try to match a new field
const fieldMatch = trimmedLine.match(/(\w+)\s*=\s*{(.+?)},?$/);
if (fieldMatch?.[1] && fieldMatch?.[2]) {
// Save previous field if exists
if (currentField) {
fields[currentField] = buffer.trim();
}
// Start new field
currentField = fieldMatch[1].toLowerCase();
buffer = fieldMatch[2];
} else if (currentField) {
// Continue previous field
buffer += ' ' + trimmedLine.replace(/},$/, '');
}
}
// Save last field if exists
if (currentField) {
fields[currentField] = buffer.trim();
}
return { type, fields };
}
export function parseBibtex(bibtex: string): Publication[] {
const entries = bibtex
.split('@')
.slice(1) // Skip first empty element
.map(entry => parseBibTeXEntry(entry))
.filter((entry): entry is BibTeXEntry => entry !== null);
return entries.map(entry => {
const publicationType =
entry.type === 'inproceedings' ? 'conference' :
entry.type === 'article' ? 'journal' :
entry.type === 'mastersthesis' ? 'thesis' : 'workshop';
return {
title: entry.fields.title?.replace(/[{}]/g, '') || '',
authors: parseAuthors(entry.fields.author || ''),
venue: entry.fields.booktitle || entry.fields.journal || '',
year: parseInt(entry.fields.year || '0', 10),
doi: entry.fields.doi,
url: entry.fields.url,
paperUrl: entry.fields.paperurl,
posterUrl: entry.fields.posterurl,
abstract: entry.fields.abstract,
type: publicationType
};
});
}