fixed some bugs

This commit is contained in:
chark1es 2025-02-03 13:04:43 -08:00
parent 6df69275b9
commit 3181b0179d
6 changed files with 967 additions and 1474 deletions

View file

@ -18,8 +18,6 @@
"motion": "^11.15.0",
"next": "^15.1.2",
"pocketbase": "^0.25.1",
"react": "^18.3.1",
"react-dom": "^18.3.1",
"react-icons": "^5.4.0",
"tailwindcss": "^3.4.16",
},

256
package-lock.json generated
View file

@ -10,21 +10,26 @@
"dependencies": {
"@astrojs/mdx": "4.0.3",
"@astrojs/node": "^9.0.0",
"@astrojs/react": "4.1.2",
"@astrojs/react": "^4.2.0",
"@astrojs/tailwind": "5.1.4",
"@types/react": "^18.3.14",
"@types/react-dom": "^18.3.2",
"@types/js-yaml": "^4.0.9",
"@types/react": "^19.0.8",
"@types/react-dom": "^19.0.3",
"astro": "5.1.1",
"astro-expressive-code": "^0.38.3",
"astro-icon": "^1.1.4",
"js-yaml": "^4.1.0",
"jszip": "^3.10.1",
"motion": "^11.15.0",
"next": "^15.1.2",
"react": "^18.3.1",
"react-dom": "^18.3.1",
"pocketbase": "^0.25.1",
"react": "^19.0.0",
"react-dom": "^19.0.0",
"react-icons": "^5.4.0",
"tailwindcss": "^3.4.16"
},
"devDependencies": {
"daisyui": "^4.12.23",
"prettier": "^3.4.2",
"prettier-plugin-astro": "^0.14.1",
"tailwindcss-animated": "^1.1.2",
@ -170,14 +175,13 @@
}
},
"node_modules/@astrojs/react": {
"version": "4.1.2",
"resolved": "https://registry.npmjs.org/@astrojs/react/-/react-4.1.2.tgz",
"integrity": "sha512-Slw8Bho50w1+rYnSnDl5PDAUikSOEItx5DEJU5OgmarTirBr1audIb2DgC8faAGcGkq5WhuUVsSiq/TmSORlwA==",
"license": "MIT",
"version": "4.2.0",
"resolved": "https://registry.npmjs.org/@astrojs/react/-/react-4.2.0.tgz",
"integrity": "sha512-2OccnYFK+mLuy9GpJqPM3BQGvvemnXNeww+nBVYFuiH04L7YIdfg4Gq0LT7v/BraiuADV5uTl9VhTDL/ZQPAhw==",
"dependencies": {
"@vitejs/plugin-react": "^4.3.4",
"ultrahtml": "^1.5.3",
"vite": "^6.0.5"
"vite": "^6.0.9"
},
"engines": {
"node": "^18.17.1 || ^20.3.0 || >=22.0.0"
@ -2137,6 +2141,11 @@
"@types/unist": "*"
}
},
"node_modules/@types/js-yaml": {
"version": "4.0.9",
"resolved": "https://registry.npmjs.org/@types/js-yaml/-/js-yaml-4.0.9.tgz",
"integrity": "sha512-k4MGaQl5TGo/iipqb2UDG2UwjXziSWkh0uysQelTlJpX1qGlpUZYm8PnO4DxG1qBomtJUdYJ6qR6xdIah10JLg=="
},
"node_modules/@types/mdast": {
"version": "4.0.4",
"resolved": "https://registry.npmjs.org/@types/mdast/-/mdast-4.0.4.tgz",
@ -2176,29 +2185,20 @@
"undici-types": "~6.20.0"
}
},
"node_modules/@types/prop-types": {
"version": "15.7.14",
"resolved": "https://registry.npmjs.org/@types/prop-types/-/prop-types-15.7.14.tgz",
"integrity": "sha512-gNMvNH49DJ7OJYv+KAKn0Xp45p8PLl6zo2YnvDIbTd4J6MER2BmWN49TG7n9LvkyihINxeKW8+3bfS2yDC9dzQ==",
"license": "MIT"
},
"node_modules/@types/react": {
"version": "18.3.18",
"resolved": "https://registry.npmjs.org/@types/react/-/react-18.3.18.tgz",
"integrity": "sha512-t4yC+vtgnkYjNSKlFx1jkAhH8LgTo2N/7Qvi83kdEaUtMDiwpbLAktKDaAMlRcJ5eSxZkH74eEGt1ky31d7kfQ==",
"license": "MIT",
"version": "19.0.8",
"resolved": "https://registry.npmjs.org/@types/react/-/react-19.0.8.tgz",
"integrity": "sha512-9P/o1IGdfmQxrujGbIMDyYaaCykhLKc0NGCtYcECNUr9UAaDe4gwvV9bR6tvd5Br1SG0j+PBpbKr2UYY8CwqSw==",
"dependencies": {
"@types/prop-types": "*",
"csstype": "^3.0.2"
}
},
"node_modules/@types/react-dom": {
"version": "18.3.5",
"resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-18.3.5.tgz",
"integrity": "sha512-P4t6saawp+b/dFrUr2cvkVsfvPguwsxtH6dNIYRllMsefqFzkZk5UIjzyDOv5g1dXIPdG4Sp1yCR4Z6RCUsG/Q==",
"license": "MIT",
"version": "19.0.3",
"resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-19.0.3.tgz",
"integrity": "sha512-0Knk+HJiMP/qOZgMyNFamlIjw9OFCsyC2ZbigmEEyXXixgre6IQpm/4V+r3qH4GC1JPvRJKInw+on2rV6YZLeA==",
"peerDependencies": {
"@types/react": "^18.0.0"
"@types/react": "^19.0.0"
}
},
"node_modules/@types/tar": {
@ -3085,6 +3085,11 @@
"integrity": "sha512-+W7VmiVINB+ywl1HGXJXmrqkOhpKrIiVZV6tQuV54ZyQC7MMuBt81Vc336GMLoHBq5hV/F9eXgt5Mnx0Rha5Fg==",
"license": "MIT"
},
"node_modules/core-util-is": {
"version": "1.0.3",
"resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.3.tgz",
"integrity": "sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ=="
},
"node_modules/cross-spawn": {
"version": "7.0.6",
"resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz",
@ -3140,6 +3145,16 @@
],
"license": "MIT"
},
"node_modules/css-selector-tokenizer": {
"version": "0.8.0",
"resolved": "https://registry.npmjs.org/css-selector-tokenizer/-/css-selector-tokenizer-0.8.0.tgz",
"integrity": "sha512-Jd6Ig3/pe62/qe5SBPTN8h8LeUg/pT4lLgtavPf7updwwHpvFzxvOQBHYj2LZDMjUnBzgvIUSjRcf6oT5HzHFg==",
"dev": true,
"dependencies": {
"cssesc": "^3.0.0",
"fastparse": "^1.1.2"
}
},
"node_modules/css-tree": {
"version": "2.3.1",
"resolved": "https://registry.npmjs.org/css-tree/-/css-tree-2.3.1.tgz",
@ -3216,6 +3231,34 @@
"integrity": "sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==",
"license": "MIT"
},
"node_modules/culori": {
"version": "3.3.0",
"resolved": "https://registry.npmjs.org/culori/-/culori-3.3.0.tgz",
"integrity": "sha512-pHJg+jbuFsCjz9iclQBqyL3B2HLCBF71BwVNujUYEvCeQMvV97R59MNK3R2+jgJ3a1fcZgI9B3vYgz8lzr/BFQ==",
"dev": true,
"engines": {
"node": "^12.20.0 || ^14.13.1 || >=16.0.0"
}
},
"node_modules/daisyui": {
"version": "4.12.23",
"resolved": "https://registry.npmjs.org/daisyui/-/daisyui-4.12.23.tgz",
"integrity": "sha512-EM38duvxutJ5PD65lO/AFMpcw+9qEy6XAZrTpzp7WyaPeO/l+F/Qiq0ECHHmFNcFXh5aVoALY4MGrrxtCiaQCQ==",
"dev": true,
"dependencies": {
"css-selector-tokenizer": "^0.8",
"culori": "^3",
"picocolors": "^1",
"postcss-js": "^4"
},
"engines": {
"node": ">=16.9.0"
},
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/daisyui"
}
},
"node_modules/debug": {
"version": "4.4.0",
"resolved": "https://registry.npmjs.org/debug/-/debug-4.4.0.tgz",
@ -3783,6 +3826,12 @@
"node": ">=8.6.0"
}
},
"node_modules/fastparse": {
"version": "1.1.2",
"resolved": "https://registry.npmjs.org/fastparse/-/fastparse-1.1.2.tgz",
"integrity": "sha512-483XLLxTVIwWK3QTrMGRqUfUpoOs/0hbQrl2oz4J0pAcm3A3bu84wxTFqGqkJzewCLdME38xJLJAxBABfQT8sQ==",
"dev": true
},
"node_modules/fastq": {
"version": "1.18.0",
"resolved": "https://registry.npmjs.org/fastq/-/fastq-1.18.0.tgz",
@ -3921,13 +3970,12 @@
}
},
"node_modules/framer-motion": {
"version": "11.15.0",
"resolved": "https://registry.npmjs.org/framer-motion/-/framer-motion-11.15.0.tgz",
"integrity": "sha512-MLk8IvZntxOMg7lDBLw2qgTHHv664bYoYmnFTmE0Gm/FW67aOJk0WM3ctMcG+Xhcv+vh5uyyXwxvxhSeJzSe+w==",
"license": "MIT",
"version": "11.18.2",
"resolved": "https://registry.npmjs.org/framer-motion/-/framer-motion-11.18.2.tgz",
"integrity": "sha512-5F5Och7wrvtLVElIpclDT0CBzMVg3dL22B64aZwHtsIY8RB4mXICLrkajK4G9R+ieSAGcgrLeae2SeUTg2pr6w==",
"dependencies": {
"motion-dom": "^11.14.3",
"motion-utils": "^11.14.3",
"motion-dom": "^11.18.1",
"motion-utils": "^11.18.1",
"tslib": "^2.4.0"
},
"peerDependencies": {
@ -4506,6 +4554,11 @@
"node": ">=0.10.0"
}
},
"node_modules/immediate": {
"version": "3.0.6",
"resolved": "https://registry.npmjs.org/immediate/-/immediate-3.0.6.tgz",
"integrity": "sha512-XXOFtyqDjNDAQxVfYxuF7g9Il/IbWmmlQg2MYKOH8ExIT1qg6xc4zyS3HaEEATgs1btfzxq15ciUiY7gjSXRGQ=="
},
"node_modules/import-meta-resolve": {
"version": "4.1.0",
"resolved": "https://registry.npmjs.org/import-meta-resolve/-/import-meta-resolve-4.1.0.tgz",
@ -4714,6 +4767,11 @@
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/isarray": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz",
"integrity": "sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ=="
},
"node_modules/isexe": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz",
@ -4786,6 +4844,17 @@
"node": ">=6"
}
},
"node_modules/jszip": {
"version": "3.10.1",
"resolved": "https://registry.npmjs.org/jszip/-/jszip-3.10.1.tgz",
"integrity": "sha512-xXDvecyTpGLrqFrvkrUSoxxfJI5AH7U8zxxtVclpsUtMCq4JQ290LY8AW5c7Ggnr/Y/oK+bQMbqK2qmtk3pN4g==",
"dependencies": {
"lie": "~3.3.0",
"pako": "~1.0.2",
"readable-stream": "~2.3.6",
"setimmediate": "^1.0.5"
}
},
"node_modules/kleur": {
"version": "4.1.5",
"resolved": "https://registry.npmjs.org/kleur/-/kleur-4.1.5.tgz",
@ -4801,6 +4870,14 @@
"integrity": "sha512-Y+60/zizpJ3HRH8DCss+q95yr6145JXZo46OTpFvDZWLfRCE4qChOyk1b26nMaNpfHHgxagk9dXT5OP0Tfe+dQ==",
"license": "MIT"
},
"node_modules/lie": {
"version": "3.3.0",
"resolved": "https://registry.npmjs.org/lie/-/lie-3.3.0.tgz",
"integrity": "sha512-UaiMJzeWRlEujzAuw5LokY1L5ecNQYZKfmyZ9L7wDHb/p5etKaxXhohBcrw0EYby+G/NA52vRSN4N39dxHAIwQ==",
"dependencies": {
"immediate": "~3.0.5"
}
},
"node_modules/lilconfig": {
"version": "3.1.3",
"resolved": "https://registry.npmjs.org/lilconfig/-/lilconfig-3.1.3.tgz",
@ -4903,18 +4980,6 @@
"url": "https://github.com/sponsors/wooorm"
}
},
"node_modules/loose-envify": {
"version": "1.4.0",
"resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz",
"integrity": "sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==",
"license": "MIT",
"dependencies": {
"js-tokens": "^3.0.0 || ^4.0.0"
},
"bin": {
"loose-envify": "cli.js"
}
},
"node_modules/lru-cache": {
"version": "5.1.1",
"resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz",
@ -6166,16 +6231,17 @@
}
},
"node_modules/motion-dom": {
"version": "11.14.3",
"resolved": "https://registry.npmjs.org/motion-dom/-/motion-dom-11.14.3.tgz",
"integrity": "sha512-lW+D2wBy5vxLJi6aCP0xyxTxlTfiu+b+zcpVbGVFUxotwThqhdpPRSmX8xztAgtZMPMeU0WGVn/k1w4I+TbPqA==",
"license": "MIT"
"version": "11.18.1",
"resolved": "https://registry.npmjs.org/motion-dom/-/motion-dom-11.18.1.tgz",
"integrity": "sha512-g76KvA001z+atjfxczdRtw/RXOM3OMSdd1f4DL77qCTF/+avrRJiawSG4yDibEQ215sr9kpinSlX2pCTJ9zbhw==",
"dependencies": {
"motion-utils": "^11.18.1"
}
},
"node_modules/motion-utils": {
"version": "11.14.3",
"resolved": "https://registry.npmjs.org/motion-utils/-/motion-utils-11.14.3.tgz",
"integrity": "sha512-Xg+8xnqIJTpr0L/cidfTTBFkvRw26ZtGGuIhA94J9PQ2p4mEa06Xx7QVYZH0BP+EpMSaDlu+q0I0mmvwADPsaQ==",
"license": "MIT"
"version": "11.18.1",
"resolved": "https://registry.npmjs.org/motion-utils/-/motion-utils-11.18.1.tgz",
"integrity": "sha512-49Kt+HKjtbJKLtgO/LKj9Ld+6vw9BjH5d9sc40R/kVyH8GLAXgT42M2NnuPcJNuA3s9ZfZBUcwIgpmZWGEE+hA=="
},
"node_modules/mrmime": {
"version": "2.0.0",
@ -6525,6 +6591,11 @@
"integrity": "sha512-ts9KSdroZisdvKMWVAVCXiKqnqNfXz4+IbrBG8/BWx/TR5le+jfenvoBuIZ6UWM9nz47W7AbD9qYfAwfWMIwzA==",
"license": "MIT"
},
"node_modules/pako": {
"version": "1.0.11",
"resolved": "https://registry.npmjs.org/pako/-/pako-1.0.11.tgz",
"integrity": "sha512-4hLB8Py4zZce5s4yd9XzopqwVv/yGNhV1Bl8NTmCq1763HeK2+EwVTv+leGeL13Dnh2wfbqowVPXCIO0z4taYw=="
},
"node_modules/parse-entities": {
"version": "4.0.2",
"resolved": "https://registry.npmjs.org/parse-entities/-/parse-entities-4.0.2.tgz",
@ -6731,6 +6802,11 @@
"pathe": "^1.1.2"
}
},
"node_modules/pocketbase": {
"version": "0.25.1",
"resolved": "https://registry.npmjs.org/pocketbase/-/pocketbase-0.25.1.tgz",
"integrity": "sha512-2IH0KLI/qMNR/E17C7BGWX2FxW7Tead+igLHOWZ45P56v/NyVT18Jnmddeft+3qWWGL1Hog2F8bc4orWV/+Fcg=="
},
"node_modules/postcss": {
"version": "8.4.49",
"resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.49.tgz",
@ -6928,6 +7004,11 @@
"node": ">=6"
}
},
"node_modules/process-nextick-args": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz",
"integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag=="
},
"node_modules/prompts": {
"version": "2.4.2",
"resolved": "https://registry.npmjs.org/prompts/-/prompts-2.4.2.tgz",
@ -7012,28 +7093,22 @@
}
},
"node_modules/react": {
"version": "18.3.1",
"resolved": "https://registry.npmjs.org/react/-/react-18.3.1.tgz",
"integrity": "sha512-wS+hAgJShR0KhEvPJArfuPVN1+Hz1t0Y6n5jLrGQbkb4urgPE/0Rve+1kMB1v/oWgHgm4WIcV+i7F2pTVj+2iQ==",
"license": "MIT",
"dependencies": {
"loose-envify": "^1.1.0"
},
"version": "19.0.0",
"resolved": "https://registry.npmjs.org/react/-/react-19.0.0.tgz",
"integrity": "sha512-V8AVnmPIICiWpGfm6GLzCR/W5FXLchHop40W4nXBmdlEceh16rCN8O8LNWm5bh5XUX91fh7KpA+W0TgMKmgTpQ==",
"engines": {
"node": ">=0.10.0"
}
},
"node_modules/react-dom": {
"version": "18.3.1",
"resolved": "https://registry.npmjs.org/react-dom/-/react-dom-18.3.1.tgz",
"integrity": "sha512-5m4nQKp+rZRb09LNH59GM4BxTh9251/ylbKIbpe7TpGxfJ+9kv6BLkLBXIjjspbgbnIBNqlI23tRnTWT0snUIw==",
"license": "MIT",
"version": "19.0.0",
"resolved": "https://registry.npmjs.org/react-dom/-/react-dom-19.0.0.tgz",
"integrity": "sha512-4GV5sHFG0e/0AD4X+ySy6UJd3jVl1iNsNHdpad0qhABJ11twS3TTBnseqsKurKcsNqCEFeGL3uLpVChpIO3QfQ==",
"dependencies": {
"loose-envify": "^1.1.0",
"scheduler": "^0.23.2"
"scheduler": "^0.25.0"
},
"peerDependencies": {
"react": "^18.3.1"
"react": "^19.0.0"
}
},
"node_modules/react-icons": {
@ -7063,6 +7138,20 @@
"pify": "^2.3.0"
}
},
"node_modules/readable-stream": {
"version": "2.3.8",
"resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.8.tgz",
"integrity": "sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA==",
"dependencies": {
"core-util-is": "~1.0.0",
"inherits": "~2.0.3",
"isarray": "~1.0.0",
"process-nextick-args": "~2.0.0",
"safe-buffer": "~5.1.1",
"string_decoder": "~1.1.1",
"util-deprecate": "~1.0.1"
}
},
"node_modules/readdirp": {
"version": "3.6.0",
"resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz",
@ -7515,6 +7604,11 @@
"dev": true,
"license": "MIT"
},
"node_modules/safe-buffer": {
"version": "5.1.2",
"resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz",
"integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g=="
},
"node_modules/safer-buffer": {
"version": "2.1.2",
"resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz",
@ -7532,13 +7626,9 @@
}
},
"node_modules/scheduler": {
"version": "0.23.2",
"resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.23.2.tgz",
"integrity": "sha512-UOShsPwz7NrMUqhR6t0hWjFduvOzbtv7toDH1/hIrfRNIDBnnBWd0CwJTGvTpngVlmwGCdP9/Zl/tVrDqcuYzQ==",
"license": "MIT",
"dependencies": {
"loose-envify": "^1.1.0"
}
"version": "0.25.0",
"resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.25.0.tgz",
"integrity": "sha512-xFVuu11jh+xcO7JOAGJNOXld8/TcEHK/4CituBUeUb5hqxJLj9YuemAEuvm9gQ/+pgXYfbQuqAkiYu+u7YEsNA=="
},
"node_modules/semver": {
"version": "6.3.1",
@ -7578,6 +7668,11 @@
"integrity": "sha512-rb+9B5YBIEzYcD6x2VKidaa+cqYBJQKnU4oe4E3ANwRRN56yk/ua1YCJT1n21NTS8w6CcOclAKNP3PhdCXKYtQ==",
"license": "ISC"
},
"node_modules/setimmediate": {
"version": "1.0.5",
"resolved": "https://registry.npmjs.org/setimmediate/-/setimmediate-1.0.5.tgz",
"integrity": "sha512-MATJdZp8sLqDl/68LfQmbP8zKPLQNV6BIZoIgrscFDQ+RsvK/BxeDQOgyxKKoh0y/8h3BqVFnCqQ/gd+reiIXA=="
},
"node_modules/setprototypeof": {
"version": "1.2.0",
"resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz",
@ -7753,6 +7848,14 @@
"node": ">=10.0.0"
}
},
"node_modules/string_decoder": {
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz",
"integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==",
"dependencies": {
"safe-buffer": "~5.1.0"
}
},
"node_modules/string-width": {
"version": "7.2.0",
"resolved": "https://registry.npmjs.org/string-width/-/string-width-7.2.0.tgz",
@ -8587,10 +8690,9 @@
}
},
"node_modules/vite": {
"version": "6.0.7",
"resolved": "https://registry.npmjs.org/vite/-/vite-6.0.7.tgz",
"integrity": "sha512-RDt8r/7qx9940f8FcOIAH9PTViRrghKaK2K1jY3RaAURrEUbm9Du1mJ72G+jlhtG3WwodnfzY8ORQZbBavZEAQ==",
"license": "MIT",
"version": "6.0.11",
"resolved": "https://registry.npmjs.org/vite/-/vite-6.0.11.tgz",
"integrity": "sha512-4VL9mQPKoHy4+FE0NnRE/kbY51TOfaknxAjt3fJbGJxhIpBZiqVzlZDEesWWsuREXHwNdAoOFZ9MkPEVXczHwg==",
"dependencies": {
"esbuild": "^0.24.2",
"postcss": "^8.4.49",

View file

@ -12,11 +12,11 @@
"dependencies": {
"@astrojs/mdx": "4.0.3",
"@astrojs/node": "^9.0.0",
"@astrojs/react": "4.1.2",
"@astrojs/react": "^4.2.0",
"@astrojs/tailwind": "5.1.4",
"@types/js-yaml": "^4.0.9",
"@types/react": "^18.3.14",
"@types/react-dom": "^18.3.2",
"@types/react": "^19.0.8",
"@types/react-dom": "^19.0.3",
"astro": "5.1.1",
"astro-expressive-code": "^0.38.3",
"astro-icon": "^1.1.4",
@ -25,8 +25,8 @@
"motion": "^11.15.0",
"next": "^15.1.2",
"pocketbase": "^0.25.1",
"react": "^18.3.1",
"react-dom": "^18.3.1",
"react": "^19.0.0",
"react-dom": "^19.0.0",
"react-icons": "^5.4.0",
"tailwindcss": "^3.4.16"
},

View file

@ -1,568 +0,0 @@
---
interface Props {
id?: string;
title?: string;
className?: string;
}
const {
id = "filePreviewModal",
title = "File Preview",
className = "",
} = Astro.props;
---
<dialog id={id} class={`modal ${className}`}>
<!-- Single File Preview -->
<div id={`${id}-single`} class="modal-box w-11/12 max-w-5xl h-[80vh]">
<div class="flex justify-between items-center mb-4">
<h3 class="font-bold text-lg" id={`${id}-title`}>{title}</h3>
<div class="flex items-center gap-2">
<a
id={`${id}-external-link`}
href="#"
target="_blank"
class="btn btn-sm btn-ghost"
>
<svg
xmlns="http://www.w3.org/2000/svg"
class="h-4 w-4 mr-1"
viewBox="0 0 20 20"
fill="currentColor"
>
<path
d="M11 3a1 1 0 100 2h2.586l-6.293 6.293a1 1 0 101.414 1.414L15 6.414V9a1 1 0 102 0V4a1 1 0 00-1-1h-5z"
></path>
<path
d="M5 5a2 2 0 00-2 2v8a2 2 0 002 2h8a2 2 0 002-2v-3a1 1 0 10-2 0v3H5V7h3a1 1 0 000-2H5z"
></path>
</svg>
Open in New Tab
</a>
<form method="dialog">
<button class="btn btn-sm btn-circle btn-ghost">✕</button>
</form>
</div>
</div>
<div class="h-[calc(100%-4rem)] bg-base-200 rounded-lg">
<!-- Loading Skeleton -->
<div id={`${id}-loading`} class="w-full h-full hidden">
<div class="flex flex-col items-center justify-center h-full">
<div
class="loading loading-spinner loading-lg text-primary"
>
</div>
<p class="mt-4 text-base-content/70">Loading preview...</p>
</div>
</div>
<!-- Preview Container -->
<div id={`${id}-preview-container`} class="w-full h-full"></div>
</div>
</div>
<!-- Multiple Files Selection -->
<div id={`${id}-multiple`} class="modal-box w-11/12 max-w-5xl hidden">
<div class="flex justify-between items-center mb-4">
<h3 class="font-bold text-lg">Select File to Preview</h3>
<form method="dialog">
<button class="btn btn-sm btn-circle btn-ghost">✕</button>
</form>
</div>
<!-- Multiple Files Loading Skeleton -->
<div id={`${id}-multiple-loading`} class="hidden">
<div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-4">
{
Array(6)
.fill(0)
.map(() => (
<div class="card bg-base-200 animate-pulse">
<div class="aspect-video bg-base-300 rounded-t-lg" />
<div class="card-body p-4">
<div class="h-4 bg-base-300 rounded w-3/4" />
<div class="h-3 bg-base-300 rounded w-1/2 mt-2 opacity-70" />
</div>
</div>
))
}
</div>
</div>
<!-- Files Grid -->
<div
class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-4"
id={`${id}-file-grid`}
>
<!-- Files will be dynamically inserted here -->
</div>
</div>
<form method="dialog" class="modal-backdrop">
<button>close</button>
</form>
</dialog>
<script define:vars={{ id }}>
class FilePreviewModal {
constructor(modalId) {
this.modal = document.getElementById(modalId);
this.singleView = document.getElementById(`${modalId}-single`);
this.multipleView = document.getElementById(`${modalId}-multiple`);
this.previewContainer = document.getElementById(
`${modalId}-preview-container`
);
this.fileGrid = document.getElementById(`${modalId}-file-grid`);
this.titleElement = document.getElementById(`${modalId}-title`);
this.externalLink = document.getElementById(
`${modalId}-external-link`
);
this.loadingElement = document.getElementById(`${modalId}-loading`);
this.multipleLoadingElement = document.getElementById(
`${modalId}-multiple-loading`
);
// Close modal when clicking backdrop
this.modal.addEventListener("click", (e) => {
const dialogDimensions = this.modal.getBoundingClientRect();
if (
e.clientX < dialogDimensions.left ||
e.clientX > dialogDimensions.right ||
e.clientY < dialogDimensions.top ||
e.clientY > dialogDimensions.bottom
) {
this.modal.close();
}
});
}
getFileType(fileName) {
const ext = fileName.split(".").pop()?.toLowerCase() || "";
// Images
if (
["jpg", "jpeg", "png", "gif", "webp", "svg", "bmp"].includes(
ext
)
)
return "image";
// Videos
if (["mp4", "webm", "ogg", "mov", "avi"].includes(ext))
return "video";
// Audio
if (["mp3", "wav", "ogg", "m4a", "aac"].includes(ext))
return "audio";
// Documents
if (["pdf"].includes(ext)) return "pdf";
if (["doc", "docx"].includes(ext)) return "word";
if (["xls", "xlsx"].includes(ext)) return "excel";
if (["ppt", "pptx"].includes(ext)) return "powerpoint";
if (["txt", "rtf", "md"].includes(ext)) return "text";
// Archives
if (["zip", "rar", "7z", "tar", "gz"].includes(ext))
return "archive";
// Code
if (
[
"js",
"ts",
"py",
"java",
"cpp",
"c",
"cs",
"html",
"css",
"php",
"rb",
].includes(ext)
)
return "code";
return "other";
}
createPreviewElement(file) {
const fileType = this.getFileType(file.name);
let element;
switch (fileType) {
case "image":
element = document.createElement("img");
element.className = "w-full h-full object-contain";
element.src = file.url;
element.alt = file.name;
break;
case "video":
element = document.createElement("video");
element.className = "w-full h-full";
element.setAttribute("controls", "");
element.setAttribute("controlsList", "nodownload");
element.setAttribute("preload", "metadata");
element.src = file.url;
break;
case "audio":
element = document.createElement("div");
element.className =
"w-full h-full flex flex-col items-center justify-center p-8 gap-4";
// Add audio icon
element.innerHTML = `
<svg xmlns="http://www.w3.org/2000/svg" class="h-24 w-24 text-base-content/50" viewBox="0 0 20 20" fill="currentColor">
<path fill-rule="evenodd" d="M9.383 3.076A1 1 0 0110 4v12a1 1 0 01-1.707.707L4.586 13H2a1 1 0 01-1-1V8a1 1 0 011-1h2.586l3.707-3.707a1 1 0 011.09-.217zM14.657 2.929a1 1 0 011.414 0A9.972 9.972 0 0119 10a9.972 9.972 0 01-2.929 7.071 1 1 0 01-1.414-1.414A7.971 7.971 0 0017 10c0-2.21-.894-4.208-2.343-5.657a1 1 0 010-1.414zm-2.829 2.828a1 1 0 011.415 0A5.983 5.983 0 0115 10a5.984 5.984 0 01-1.757 4.243 1 1 0 01-1.415-1.415A3.984 3.984 0 0013 10a3.983 3.983 0 00-1.172-2.828 1 1 0 010-1.415z" clip-rule="evenodd" />
</svg>`;
const audio = document.createElement("audio");
audio.className = "w-full max-w-md";
audio.setAttribute("controls", "");
audio.setAttribute("controlsList", "nodownload");
audio.src = file.url;
element.appendChild(audio);
break;
case "pdf":
element = document.createElement("embed");
element.className = "w-full h-full";
element.type = "application/pdf";
element.src = file.url + "#toolbar=0&navpanes=0";
break;
case "word":
case "excel":
case "powerpoint":
case "text":
case "code":
// For documents that might be previewable, offer both preview and download
element = document.createElement("div");
element.className =
"w-full h-full flex flex-col items-center justify-center p-8 gap-4";
const icon = this.getFileTypeIcon(fileType);
element.innerHTML = `
<div class="text-center">
${icon}
<p class="mt-4 font-medium">${file.name}</p>
<p class="text-base-content/70 mb-4">This file type may not be previewable in the browser</p>
<div class="flex gap-2">
<a href="${file.url}" target="_blank" class="btn btn-primary btn-sm">
<svg xmlns="http://www.w3.org/2000/svg" class="h-4 w-4 mr-2" viewBox="0 0 20 20" fill="currentColor">
<path d="M11 3a1 1 0 100 2h2.586l-6.293 6.293a1 1 0 101.414 1.414L15 6.414V9a1 1 0 102 0V4a1 1 0 00-1-1h-5z" />
</svg>
Open in New Tab
</a>
<a href="${file.url}" download class="btn btn-ghost btn-sm">
<svg xmlns="http://www.w3.org/2000/svg" class="h-4 w-4 mr-2" viewBox="0 0 20 20" fill="currentColor">
<path fill-rule="evenodd" d="M3 17a1 1 0 011-1h12a1 1 0 110 2H4a1 1 0 01-1-1zm3.293-7.707a1 1 0 011.414 0L9 10.586V3a1 1 0 112 0v7.586l1.293-1.293a1 1 0 111.414 1.414l-3 3a1 1 0 01-1.414 0l-3-3a1 1 0 010-1.414z" clip-rule="evenodd" />
</svg>
Download
</a>
</div>
</div>
`;
break;
default:
// For other file types, show a download option
element = document.createElement("div");
element.className =
"w-full h-full flex items-center justify-center";
element.innerHTML = `
<div class="text-center">
<svg xmlns="http://www.w3.org/2000/svg" class="h-16 w-16 mx-auto mb-4 text-base-content/50" viewBox="0 0 20 20" fill="currentColor">
<path fill-rule="evenodd" d="M4 4a2 2 0 012-2h4.586A2 2 0 0112 2.586L15.414 6A2 2 0 0116 7.414V16a2 2 0 01-2 2H6a2 2 0 01-2-2V4z" clip-rule="evenodd" />
</svg>
<p class="mb-4">${file.name}</p>
<a href="${file.url}" download class="btn btn-primary btn-sm">
<svg xmlns="http://www.w3.org/2000/svg" class="h-4 w-4 mr-2" viewBox="0 0 20 20" fill="currentColor">
<path fill-rule="evenodd" d="M3 17a1 1 0 011-1h12a1 1 0 110 2H4a1 1 0 01-1-1zm3.293-7.707a1 1 0 011.414 0L9 10.586V3a1 1 0 112 0v7.586l1.293-1.293a1 1 0 111.414 1.414l-3 3a1 1 0 01-1.414 0l-3-3a1 1 0 010-1.414z" clip-rule="evenodd" />
</svg>
Download File
</a>
</div>
`;
}
return element;
}
getFileTypeIcon(fileType) {
const iconClasses = "h-24 w-24 mx-auto text-base-content/50";
switch (fileType) {
case "word":
return `<svg xmlns="http://www.w3.org/2000/svg" class="${iconClasses}" viewBox="0 0 384 512">
<path fill="currentColor" d="M48 448V64c0-8.8 7.2-16 16-16h256c8.8 0 16 7.2 16 16v384c0 8.8-7.2 16-16 16H64c-8.8 0-16-7.2-16-16zm16-64h256V80H64v304zm48-208h160c8.8 0 16 7.2 16 16s-7.2 16-16 16H112c-8.8 0-16-7.2-16-16s7.2-16 16-16zm0 64h160c8.8 0 16 7.2 16 16s-7.2 16-16 16H112c-8.8 0-16-7.2-16-16s7.2-16 16-16zm0 64h160c8.8 0 16 7.2 16 16s-7.2 16-16 16H112c-8.8 0-16-7.2-16-16s7.2-16 16-16z"/>
</svg>`;
case "excel":
return `<svg xmlns="http://www.w3.org/2000/svg" class="${iconClasses}" viewBox="0 0 384 512">
<path fill="currentColor" d="M48 448V64c0-8.8 7.2-16 16-16h256c8.8 0 16 7.2 16 16v384c0 8.8-7.2 16-16 16H64c-8.8 0-16-7.2-16-16zm16-64h256V80H64v304zm48-208h160c8.8 0 16 7.2 16 16s-7.2 16-16 16H112c-8.8 0-16-7.2-16-16s7.2-16 16-16zm0 128h160c8.8 0 16 7.2 16 16s-7.2 16-16 16H112c-8.8 0-16-7.2-16-16s7.2-16 16-16z"/>
</svg>`;
case "powerpoint":
return `<svg xmlns="http://www.w3.org/2000/svg" class="${iconClasses}" viewBox="0 0 384 512">
<path fill="currentColor" d="M48 448V64c0-8.8 7.2-16 16-16h256c8.8 0 16 7.2 16 16v384c0 8.8-7.2 16-16 16H64c-8.8 0-16-7.2-16-16zm16-64h256V80H64v304zm144-208c35.3 0 64 28.7 64 64s-28.7 64-64 64h-48v48c0 8.8-7.2 16-16 16s-16-7.2-16-16V192c0-8.8 7.2-16 16-16h64zm0 96c17.7 0 32-14.3 32-32s-14.3-32-32-32h-48v64h48z"/>
</svg>`;
case "text":
case "code":
return `<svg xmlns="http://www.w3.org/2000/svg" class="${iconClasses}" viewBox="0 0 384 512">
<path fill="currentColor" d="M48 448V64c0-8.8 7.2-16 16-16h256c8.8 0 16 7.2 16 16v384c0 8.8-7.2 16-16 16H64c-8.8 0-16-7.2-16-16zm16-64h256V80H64v304zm48-208h160c8.8 0 16 7.2 16 16s-7.2 16-16 16H112c-8.8 0-16-7.2-16-16s7.2-16 16-16zm0 64h160c8.8 0 16 7.2 16 16s-7.2 16-16 16H112c-8.8 0-16-7.2-16-16s7.2-16 16-16z"/>
</svg>`;
default:
return `<svg xmlns="http://www.w3.org/2000/svg" class="${iconClasses}" viewBox="0 0 20 20" fill="currentColor">
<path fill-rule="evenodd" d="M4 4a2 2 0 012-2h4.586A2 2 0 0112 2.586L15.414 6A2 2 0 0116 7.414V16a2 2 0 01-2 2H6a2 2 0 01-2-2V4z" clip-rule="evenodd" />
</svg>`;
}
}
showLoading(isMultiple = false) {
if (isMultiple) {
if (this.multipleLoadingElement) {
this.multipleLoadingElement.classList.remove("hidden");
}
if (this.fileGrid) {
this.fileGrid.classList.add("hidden");
}
} else {
if (this.loadingElement) {
this.loadingElement.classList.remove("hidden");
}
if (this.previewContainer) {
this.previewContainer.classList.add("hidden");
}
}
}
hideLoading(isMultiple = false) {
if (isMultiple) {
if (this.multipleLoadingElement) {
this.multipleLoadingElement.classList.add("hidden");
}
if (this.fileGrid) {
this.fileGrid.classList.remove("hidden");
}
} else {
if (this.loadingElement) {
this.loadingElement.classList.add("hidden");
}
if (this.previewContainer) {
this.previewContainer.classList.remove("hidden");
}
}
}
async showFile(file) {
this.titleElement.textContent = file.name;
this.externalLink.href = file.url;
// Show loading state
this.showLoading(false);
try {
// Clear previous preview
this.previewContainer.innerHTML = "";
// Create and append new preview
const previewElement = this.createPreviewElement(file);
// Only wait for images to load, PDFs and other files load in their own context
if (previewElement instanceof HTMLImageElement) {
await new Promise((resolve, reject) => {
previewElement.onload = resolve;
previewElement.onerror = reject;
// Add a timeout to prevent infinite loading
setTimeout(reject, 10000); // 10 second timeout
}).catch((error) => {
console.warn("Image load timeout or error:", error);
// Continue anyway, the image might still load
});
}
this.previewContainer.appendChild(previewElement);
} catch (error) {
console.error("Error loading file:", error);
this.previewContainer.innerHTML = `
<div class="flex items-center justify-center h-full">
<div class="text-center text-error">
<svg xmlns="http://www.w3.org/2000/svg" class="h-12 w-12 mx-auto mb-4" viewBox="0 0 20 20" fill="currentColor">
<path fill-rule="evenodd" d="M18 10a8 8 0 11-16 0 8 8 0 0116 0zm-7 4a1 1 0 11-2 0 1 1 0 012 0zm-1-9a1 1 0 00-1 1v4a1 1 0 102 0V6a1 1 0 00-1-1z" clip-rule="evenodd" />
</svg>
<p>Failed to load preview</p>
<a href="${file.url}" target="_blank" class="btn btn-primary btn-sm mt-4">Open in New Tab</a>
</div>
</div>
`;
} finally {
// Hide loading state after a short delay to ensure UI elements are ready
setTimeout(() => {
this.hideLoading(false);
}, 500);
}
// Show single view
this.singleView.classList.remove("hidden");
this.multipleView.classList.add("hidden");
}
createFileCard(file) {
const card = document.createElement("div");
card.className =
"card bg-base-200 hover:bg-base-300 cursor-pointer transition-colors";
const fileType = this.getFileType(file.name);
let preview = "";
if (fileType === "image") {
preview = `
<div class="aspect-video bg-base-300 rounded-t-lg overflow-hidden">
<img src="${file.url}" alt="${file.name}" class="w-full h-full object-cover" loading="lazy">
</div>
`;
} else {
const icon = this.getFileTypeIcon(fileType);
preview = `
<div class="aspect-video bg-base-300 rounded-t-lg flex items-center justify-center">
${icon}
</div>
`;
}
card.innerHTML = `
${preview}
<div class="card-body p-4">
<h3 class="card-title text-sm" title="${file.name}">
<span class="truncate">${file.name}</span>
</h3>
<p class="text-xs opacity-70">${this.getFileTypeLabel(fileType)}</p>
</div>
`;
return card;
}
getFileTypeLabel(fileType) {
switch (fileType) {
case "image":
return "Image";
case "video":
return "Video";
case "audio":
return "Audio";
case "pdf":
return "PDF Document";
case "word":
return "Word Document";
case "excel":
return "Excel Spreadsheet";
case "powerpoint":
return "PowerPoint Presentation";
case "text":
return "Text Document";
case "code":
return "Code File";
case "archive":
return "Archive";
default:
return "File";
}
}
async show(files) {
this.modal.showModal();
// Show loading state immediately
this.showLoading(false);
this.showLoading(true);
try {
// Normalize input to array
const fileArray = Array.isArray(files) ? files : [files];
// Handle empty files
if (fileArray.length === 0) {
this.previewContainer.innerHTML = `
<div class="flex items-center justify-center h-full">
<div class="text-center text-base-content/70">
<p>No files available</p>
</div>
</div>
`;
this.hideLoading(false);
return;
}
// Single file view
if (fileArray.length === 1) {
this.singleView.classList.remove("hidden");
this.multipleView.classList.add("hidden");
await this.showFile(fileArray[0]);
}
// Multiple files view
else {
this.singleView.classList.add("hidden");
this.multipleView.classList.remove("hidden");
// Clear and populate file grid
this.fileGrid.innerHTML = "";
// Create all file cards and handle their loading
const loadingPromises = fileArray.map(async (file) => {
const card = this.createFileCard(file);
this.fileGrid.appendChild(card);
// If it's an image, wait for it to load
if (this.getFileType(file.name) === "image") {
const img = card.querySelector("img");
if (img) {
await new Promise((resolve, reject) => {
img.onload = resolve;
img.onerror = reject;
// Add a timeout to prevent infinite loading
setTimeout(resolve, 5000); // 5 second timeout
}).catch(console.warn); // Log but don't fail if image fails to load
}
}
return card;
});
// Wait for all cards to be processed
await Promise.all(loadingPromises);
// Add click handlers after all cards are loaded
const cards = this.fileGrid.querySelectorAll(".card");
cards.forEach((card, index) => {
card.addEventListener("click", () => {
this.showFile(fileArray[index]);
this.singleView.classList.remove("hidden");
this.multipleView.classList.add("hidden");
});
});
}
} catch (error) {
console.error("Error showing files:", error);
this.previewContainer.innerHTML = `
<div class="flex items-center justify-center h-full">
<div class="text-center text-error">
<p>Failed to load files</p>
<p class="text-sm mt-2 opacity-70">${error instanceof Error ? error.message : "Unknown error"}</p>
</div>
</div>
`;
} finally {
// Hide both loading states
this.hideLoading(false);
this.hideLoading(true);
}
}
close() {
this.modal.close();
}
}
// Initialize the modal
const filePreviewModal = new FilePreviewModal(id);
// Make it available globally
window.filePreviewModal = filePreviewModal;
</script>
<style>
.hidden {
display: none !important;
}
</style>

File diff suppressed because it is too large Load diff

View file

@ -15,379 +15,358 @@ const text = yaml.load(textConfig) as any;
---
<Layout {title}>
<main class="min-h-screen bg-base-100/50 rounded-[1.5rem]">
<div class="container mx-auto px-6 py-6 mt-10">
<!-- Header Section with Breadcrumbs -->
<div class="mb-8">
<h1 class="text-4xl font-bold">Profile Dashboard</h1>
<p class="text-base-content/70 mt-2">
Manage your IEEE UCSD membership and activities
</p>
</div>
<main class="min-h-screen bg-base-100/50 rounded-[1.5rem]">
<div class="container mx-auto px-6 py-6 mt-10">
<!-- Header Section with Breadcrumbs -->
<div class="mb-8">
<h1 class="text-4xl font-bold">Profile Dashboard</h1>
<p class="text-base-content/70 mt-2">
Manage your IEEE UCSD membership and activities
</p>
</div>
<!-- Loading State -->
<div id="pageLoadingState" class="w-full">
<div class="flex flex-col items-center justify-center p-8">
<div class="loading loading-spinner loading-lg"></div>
<p class="mt-4 text-base-content/70">
Loading your profile...
</p>
</div>
</div>
<!-- Loading State -->
<div id="pageLoadingState" class="w-full">
<div class="flex flex-col items-center justify-center p-8">
<div class="loading loading-spinner loading-lg"></div>
<p class="mt-4 text-base-content/70">Loading your profile...</p>
</div>
</div>
<!-- Error State -->
<div id="pageErrorState" class="hidden w-full">
<div class="alert alert-error">
<div class="flex items-center gap-2">
<svg
xmlns="http://www.w3.org/2000/svg"
class="h-6 w-6"
viewBox="0 0 20 20"
fill="currentColor"
>
<path
fill-rule="evenodd"
d="M18 10a8 8 0 11-16 0 8 8 0 0116 0zm-7 4a1 1 0 11-2 0 1 1 0 012 0zm-1-9a1 1 0 00-1 1v4a1 1 0 102 0V6a1 1 0 00-1-1z"
clip-rule="evenodd"></path>
</svg>
<span>{text.ui.messages.auth.loginError}</span>
</div>
</div>
</div>
<!-- Not Authenticated State -->
<div id="notAuthenticatedState" class="hidden w-full">
<div class="card bg-base-100 shadow-xl">
<div class="card-body items-center text-center">
<div class="mb-6">
<svg
xmlns="http://www.w3.org/2000/svg"
class="h-16 w-16 text-base-content/30"
viewBox="0 0 20 20"
fill="currentColor"
>
<path
fill-rule="evenodd"
d="M10 9a3 3 0 100-6 3 3 0 000 6zm-7 9a7 7 0 1114 0H3z"
clip-rule="evenodd"></path>
</svg>
</div>
<h2 class="card-title text-2xl mb-2">
Sign in to Access Your Profile
</h2>
<p class="text-base-content/70 mb-6">
Please sign in with your IEEE UCSD account to view
and manage your profile.
</p>
<button
class="login-button btn btn-primary btn-lg gap-2"
>
<svg
xmlns="http://www.w3.org/2000/svg"
class="h-5 w-5"
viewBox="0 0 20 20"
fill="currentColor"
>
<path
fill-rule="evenodd"
d="M3 3a1 1 0 011 1v12a1 1 0 11-2 0V4a1 1 0 011-1zm7.707 3.293a1 1 0 010 1.414L9.414 9H17a1 1 0 110 2H9.414l1.293 1.293a1 1 0 01-1.414 1.414l-3-3a1 1 0 010-1.414l3-3a1 1 0 011.414 0z"
clip-rule="evenodd"></path>
</svg>
Sign in with IEEEUCSD SSO
</button>
</div>
</div>
</div>
<!-- Main Content Grid -->
<div
id="mainContent"
class="hidden grid grid-cols-1 xl:grid-cols-12 gap-8"
<!-- Error State -->
<div id="pageErrorState" class="hidden w-full">
<div class="alert alert-error">
<div class="flex items-center gap-2">
<svg
xmlns="http://www.w3.org/2000/svg"
class="h-6 w-6"
viewBox="0 0 20 20"
fill="currentColor"
>
<!-- Left Column - User Info -->
<div class="xl:col-span-4 2xl:col-span-3">
<div class="sticky top-[100px]">
<UserProfile />
</div>
</div>
<path
fill-rule="evenodd"
d="M18 10a8 8 0 11-16 0 8 8 0 0116 0zm-7 4a1 1 0 11-2 0 1 1 0 012 0zm-1-9a1 1 0 00-1 1v4a1 1 0 102 0V6a1 1 0 00-1-1z"
clip-rule="evenodd"></path>
</svg>
<span>{text.ui.messages.auth.loginError}</span>
</div>
</div>
</div>
<!-- Right Column - Content -->
<div class="xl:col-span-8 2xl:col-span-9">
<!-- Stats Overview -->
<div class="stats shadow w-full mb-8 bg-base-100">
<div class="stat">
<div class="stat-figure text-primary">
<svg
xmlns="http://www.w3.org/2000/svg"
class="h-8 w-8"
viewBox="0 0 20 20"
fill="currentColor"
>
<path
d="M13 6a3 3 0 11-6 0 3 3 0 016 0zM18 8a2 2 0 11-4 0 2 2 0 014 0zM14 15a4 4 0 00-8 0v3h8v-3zM6 8a2 2 0 11-4 0 2 2 0 014 0zM16 18v-3a5.972 5.972 0 00-.75-2.906A3.005 3.005 0 0119 15v3h-3zM4.75 12.094A5.973 5.973 0 004 15v3H1v-3a3 3 0 013.75-2.906z"
></path>
</svg>
</div>
<div class="stat-title">Events Attended</div>
<div
class="stat-value text-primary"
id="eventsAttendedValue"
>
-
</div>
<div class="stat-desc">Since joining</div>
</div>
<div class="stat">
<div class="stat-figure text-secondary">
<svg
xmlns="http://www.w3.org/2000/svg"
class="h-8 w-8"
viewBox="0 0 20 20"
fill="currentColor"
>
<path
fill-rule="evenodd"
d="M6.267 3.455a3.066 3.066 0 001.745-.723 3.066 3.066 0 013.976 0 3.066 3.066 0 001.745.723 3.066 3.066 0 012.812 2.812c.051.643.304 1.254.723 1.745a3.066 3.066 0 010 3.976 3.066 3.066 0 00-.723 1.745 3.066 3.066 0 01-2.812 2.812 3.066 3.066 0 00-1.745.723 3.066 3.066 0 01-3.976 0 3.066 3.066 0 00-1.745-.723 3.066 3.066 0 01-2.812-2.812 3.066 3.066 0 00-.723-1.745 3.066 3.066 0 010-3.976 3.066 3.066 0 00.723-1.745 3.066 3.066 0 012.812-2.812zm7.44 5.252a1 1 0 00-1.414-1.414L9 10.586 7.707 9.293a1 1 0 00-1.414 1.414l2 2a1 1 0 001.414 0l4-4z"
clip-rule="evenodd"></path>
</svg>
</div>
<div class="stat-title">Loyalty Points</div>
<div
class="stat-value text-secondary"
id="loyaltyPointsValue"
>
-
</div>
<div class="stat-desc" id="loyaltyPointsChange">
Loading...
</div>
</div>
<div class="stat">
<div class="stat-figure text-accent">
<svg
xmlns="http://www.w3.org/2000/svg"
class="h-8 w-8"
viewBox="0 0 20 20"
fill="currentColor"
>
<path
fill-rule="evenodd"
d="M5 3a2 2 0 00-2 2v10a2 2 0 002 2h10a2 2 0 002-2V5a2 2 0 00-2-2H5zm9 4a1 1 0 10-2 0v6a1 1 0 102 0V7zm-3 2a1 1 0 10-2 0v4a1 1 0 102 0V9zm-3 3a1 1 0 10-2 0v1a1 1 0 102 0v-1z"
clip-rule="evenodd"></path>
</svg>
</div>
<div class="stat-title">Activity Level</div>
<div
class="stat-value text-accent"
id="activityLevelValue"
>
-
</div>
<div class="stat-desc" id="activityLevelDesc">
Loading...
</div>
</div>
</div>
<!-- Main Content Tabs -->
<div class="tabs tabs-boxed mb-6" id="defaultViewTabs">
<button class="tab tab-active" data-default-tab="events"
>Events & Activities</button
>
<button class="tab" data-default-tab="settings"
>Settings</button
>
</div>
<!-- Content Areas -->
<div id="defaultView">
<DefaultProfileView />
</div>
<div id="settingsView" class="hidden">
<UserSettings />
</div>
<!--<div id="officerView" class="hidden">
<OfficerProfileView />
</div> -->
</div>
<!-- Not Authenticated State -->
<div id="notAuthenticatedState" class="hidden w-full">
<div class="card bg-base-100 shadow-xl">
<div class="card-body items-center text-center">
<div class="mb-6">
<svg
xmlns="http://www.w3.org/2000/svg"
class="h-16 w-16 text-base-content/30"
viewBox="0 0 20 20"
fill="currentColor"
>
<path
fill-rule="evenodd"
d="M10 9a3 3 0 100-6 3 3 0 000 6zm-7 9a7 7 0 1114 0H3z"
clip-rule="evenodd"></path>
</svg>
</div>
<h2 class="card-title text-2xl mb-2">
Sign in to Access Your Profile
</h2>
<p class="text-base-content/70 mb-6">
Please sign in with your IEEE UCSD account to view and manage your
profile.
</p>
<button class="login-button btn btn-primary btn-lg gap-2">
<svg
xmlns="http://www.w3.org/2000/svg"
class="h-5 w-5"
viewBox="0 0 20 20"
fill="currentColor"
>
<path
fill-rule="evenodd"
d="M3 3a1 1 0 011 1v12a1 1 0 11-2 0V4a1 1 0 011-1zm7.707 3.293a1 1 0 010 1.414L9.414 9H17a1 1 0 110 2H9.414l1.293 1.293a1 1 0 01-1.414 1.414l-3-3a1 1 0 010-1.414l3-3a1 1 0 011.414 0z"
clip-rule="evenodd"></path>
</svg>
Sign in with IEEEUCSD SSO
</button>
</div>
</div>
</div>
<!-- Main Content Grid -->
<div
id="mainContent"
class="hidden grid grid-cols-1 xl:grid-cols-12 gap-8"
>
<!-- Left Column - User Info -->
<div class="xl:col-span-4 2xl:col-span-3">
<div class="sticky top-[100px]">
<UserProfile />
</div>
</div>
<!-- Add FileViewerModal here, outside of the tab content -->
<FileViewerModal client:load />
</main>
<!-- Right Column - Content -->
<div class="xl:col-span-8 2xl:col-span-9">
<!-- Stats Overview -->
<div class="stats shadow w-full mb-8 bg-base-100">
<div class="stat">
<div class="stat-figure text-primary">
<svg
xmlns="http://www.w3.org/2000/svg"
class="h-8 w-8"
viewBox="0 0 20 20"
fill="currentColor"
>
<path
d="M13 6a3 3 0 11-6 0 3 3 0 016 0zM18 8a2 2 0 11-4 0 2 2 0 014 0zM14 15a4 4 0 00-8 0v3h8v-3zM6 8a2 2 0 11-4 0 2 2 0 014 0zM16 18v-3a5.972 5.972 0 00-.75-2.906A3.005 3.005 0 0119 15v3h-3zM4.75 12.094A5.973 5.973 0 004 15v3H1v-3a3 3 0 013.75-2.906z"
></path>
</svg>
</div>
<div class="stat-title">Events Attended</div>
<div class="stat-value text-primary" id="eventsAttendedValue">
-
</div>
<div class="stat-desc">Since joining</div>
</div>
<div class="stat">
<div class="stat-figure text-secondary">
<svg
xmlns="http://www.w3.org/2000/svg"
class="h-8 w-8"
viewBox="0 0 20 20"
fill="currentColor"
>
<path
fill-rule="evenodd"
d="M6.267 3.455a3.066 3.066 0 001.745-.723 3.066 3.066 0 013.976 0 3.066 3.066 0 001.745.723 3.066 3.066 0 012.812 2.812c.051.643.304 1.254.723 1.745a3.066 3.066 0 010 3.976 3.066 3.066 0 00-.723 1.745 3.066 3.066 0 01-2.812 2.812 3.066 3.066 0 00-1.745.723 3.066 3.066 0 01-3.976 0 3.066 3.066 0 00-1.745-.723 3.066 3.066 0 01-2.812-2.812 3.066 3.066 0 00-.723-1.745 3.066 3.066 0 010-3.976 3.066 3.066 0 00.723-1.745 3.066 3.066 0 012.812-2.812zm7.44 5.252a1 1 0 00-1.414-1.414L9 10.586 7.707 9.293a1 1 0 00-1.414 1.414l2 2a1 1 0 001.414 0l4-4z"
clip-rule="evenodd"></path>
</svg>
</div>
<div class="stat-title">Loyalty Points</div>
<div class="stat-value text-secondary" id="loyaltyPointsValue">
-
</div>
<div class="stat-desc" id="loyaltyPointsChange">Loading...</div>
</div>
<div class="stat">
<div class="stat-figure text-accent">
<svg
xmlns="http://www.w3.org/2000/svg"
class="h-8 w-8"
viewBox="0 0 20 20"
fill="currentColor"
>
<path
fill-rule="evenodd"
d="M5 3a2 2 0 00-2 2v10a2 2 0 002 2h10a2 2 0 002-2V5a2 2 0 00-2-2H5zm9 4a1 1 0 10-2 0v6a1 1 0 102 0V7zm-3 2a1 1 0 10-2 0v4a1 1 0 102 0V9zm-3 3a1 1 0 10-2 0v1a1 1 0 102 0v-1z"
clip-rule="evenodd"></path>
</svg>
</div>
<div class="stat-title">Activity Level</div>
<div class="stat-value text-accent" id="activityLevelValue">
-
</div>
<div class="stat-desc" id="activityLevelDesc">Loading...</div>
</div>
</div>
<!-- Main Content Tabs -->
<div class="tabs tabs-boxed mb-6" id="defaultViewTabs">
<button class="tab tab-active" data-default-tab="events"
>Events & Activities</button
>
<button class="tab" data-default-tab="settings">Settings</button>
</div>
<!-- Content Areas -->
<div id="defaultView">
<DefaultProfileView />
</div>
<div id="settingsView" class="hidden">
<UserSettings />
</div>
<!--<div id="officerView" class="hidden">
<OfficerProfileView />
</div> -->
</div>
</div>
</div>
<!-- Add FileViewerModal here, outside of the tab content -->
<FileViewerModal client:load />
</main>
</Layout>
<script>
import { Authentication } from "../components/pocketbase/Authentication";
import yaml from "js-yaml";
import profileConfig from "../config/profileConfig.yaml?raw";
import textConfig from "../config/text.yml?raw";
import { Authentication } from "../components/pocketbase/Authentication";
import yaml from "js-yaml";
import profileConfig from "../config/profileConfig.yaml?raw";
import textConfig from "../config/text.yml?raw";
const config = yaml.load(profileConfig) as any;
const text = yaml.load(textConfig) as any;
const auth = Authentication.getInstance();
const config = yaml.load(profileConfig) as any;
const text = yaml.load(textConfig) as any;
const auth = Authentication.getInstance();
// Initialize page state
const pageLoadingState = document.getElementById("pageLoadingState");
const pageErrorState = document.getElementById("pageErrorState");
const notAuthenticatedState = document.getElementById(
"notAuthenticatedState"
// Initialize page state
const pageLoadingState = document.getElementById("pageLoadingState");
const pageErrorState = document.getElementById("pageErrorState");
const notAuthenticatedState = document.getElementById(
"notAuthenticatedState",
);
const mainContent = document.getElementById("mainContent");
const defaultView = document.getElementById("defaultView");
const officerView = document.getElementById("officerView");
const settingsView = document.getElementById("settingsView");
// Stats elements
const eventsAttendedValue = document.getElementById("eventsAttendedValue");
const loyaltyPointsValue = document.getElementById("loyaltyPointsValue");
const loyaltyPointsChange = document.getElementById("loyaltyPointsChange");
const activityLevelValue = document.getElementById("activityLevelValue");
const activityLevelDesc = document.getElementById("activityLevelDesc");
// Show officer view if user has appropriate role
const showOfficerView = (user: any) => {
if (!user) return false;
const userRole = user.role || "member";
const roleConfig = config.roles[userRole];
return (
roleConfig?.permissions?.includes("manage") ||
roleConfig?.permissions?.includes("edit")
);
const mainContent = document.getElementById("mainContent");
const defaultView = document.getElementById("defaultView");
const officerView = document.getElementById("officerView");
const settingsView = document.getElementById("settingsView");
};
// Stats elements
const eventsAttendedValue = document.getElementById("eventsAttendedValue");
const loyaltyPointsValue = document.getElementById("loyaltyPointsValue");
const loyaltyPointsChange = document.getElementById("loyaltyPointsChange");
const activityLevelValue = document.getElementById("activityLevelValue");
const activityLevelDesc = document.getElementById("activityLevelDesc");
// Initialize page
const initializePage = async () => {
try {
// Show loading state
if (pageLoadingState) pageLoadingState.classList.remove("hidden");
if (pageErrorState) pageErrorState.classList.add("hidden");
if (notAuthenticatedState) notAuthenticatedState.classList.add("hidden");
if (mainContent) mainContent.classList.add("hidden");
// Show officer view if user has appropriate role
const showOfficerView = (user: any) => {
if (!user) return false;
const userRole = user.role || "member";
const roleConfig = config.roles[userRole];
return (
roleConfig?.permissions?.includes("manage") ||
roleConfig?.permissions?.includes("edit")
);
};
// Check auth state
if (!auth.isAuthenticated()) {
if (pageLoadingState) pageLoadingState.classList.add("hidden");
if (notAuthenticatedState)
notAuthenticatedState.classList.remove("hidden");
return;
}
// Initialize page
const initializePage = async () => {
try {
// Show loading state
if (pageLoadingState) pageLoadingState.classList.remove("hidden");
if (pageErrorState) pageErrorState.classList.add("hidden");
if (notAuthenticatedState)
notAuthenticatedState.classList.add("hidden");
if (mainContent) mainContent.classList.add("hidden");
const user = auth.getCurrentUser();
// Check auth state
if (!auth.isAuthenticated()) {
if (pageLoadingState) pageLoadingState.classList.add("hidden");
if (notAuthenticatedState)
notAuthenticatedState.classList.remove("hidden");
return;
}
// Update stats
if (eventsAttendedValue) {
const eventsAttended = user.events_attended?.length || 0;
eventsAttendedValue.textContent = eventsAttended.toString();
}
const user = auth.getCurrentUser();
if (loyaltyPointsValue && loyaltyPointsChange) {
const points = user.points || 0;
loyaltyPointsValue.textContent = points.toString();
const pointsChange = user.points_change_30d || 0;
loyaltyPointsChange.textContent =
pointsChange >= 0
? `↗︎ ${pointsChange} points (30 days)`
: `↘︎ ${Math.abs(pointsChange)} points (30 days)`;
}
// Update stats
if (eventsAttendedValue) {
const eventsAttended = user.events_attended?.length || 0;
eventsAttendedValue.textContent = eventsAttended.toString();
}
if (activityLevelValue && activityLevelDesc) {
const eventsAttended = user.events_attended?.length || 0;
const points = user.points || 0;
let activityLevel = "Low";
let description = "Occasional Member";
if (loyaltyPointsValue && loyaltyPointsChange) {
const points = user.points || 0;
loyaltyPointsValue.textContent = points.toString();
const pointsChange = user.points_change_30d || 0;
loyaltyPointsChange.textContent =
pointsChange >= 0
? `↗︎ ${pointsChange} points (30 days)`
: `↘︎ ${Math.abs(pointsChange)} points (30 days)`;
}
if (activityLevelValue && activityLevelDesc) {
const eventsAttended = user.events_attended?.length || 0;
const points = user.points || 0;
let activityLevel = "Low";
let description = "Occasional Member";
if (eventsAttended > 5 || points > 50) {
activityLevel = "High";
description = "Active Member";
} else if (eventsAttended > 2 || points > 20) {
activityLevel = "Medium";
description = "Regular Member";
}
activityLevelValue.textContent = activityLevel;
activityLevelDesc.textContent = description;
}
// Show appropriate view based on user role
if (showOfficerView(user)) {
if (officerView) officerView.classList.remove("hidden");
if (defaultView) defaultView.classList.add("hidden");
} else {
if (officerView) officerView.classList.add("hidden");
if (defaultView) defaultView.classList.remove("hidden");
}
// Hide loading state and show content
if (pageLoadingState) pageLoadingState.classList.add("hidden");
if (mainContent) mainContent.classList.remove("hidden");
} catch (error) {
console.error("Failed to initialize page:", error);
if (pageLoadingState) pageLoadingState.classList.add("hidden");
if (pageErrorState) pageErrorState.classList.remove("hidden");
if (mainContent) mainContent.classList.add("hidden");
if (eventsAttended > 5 || points > 50) {
activityLevel = "High";
description = "Active Member";
} else if (eventsAttended > 2 || points > 20) {
activityLevel = "Medium";
description = "Regular Member";
}
};
// Check on load and auth changes
activityLevelValue.textContent = activityLevel;
activityLevelDesc.textContent = description;
}
// Show appropriate view based on user role
if (showOfficerView(user)) {
if (officerView) officerView.classList.remove("hidden");
if (defaultView) defaultView.classList.add("hidden");
} else {
if (officerView) officerView.classList.add("hidden");
if (defaultView) defaultView.classList.remove("hidden");
}
// Hide loading state and show content
if (pageLoadingState) pageLoadingState.classList.add("hidden");
if (mainContent) mainContent.classList.remove("hidden");
} catch (error) {
console.error("Failed to initialize page:", error);
if (pageLoadingState) pageLoadingState.classList.add("hidden");
if (pageErrorState) pageErrorState.classList.remove("hidden");
if (mainContent) mainContent.classList.add("hidden");
}
};
// Check on load and auth changes
initializePage();
auth.onAuthStateChange(() => {
initializePage();
auth.onAuthStateChange(() => {
initializePage();
});
// Handle default view tab switching
const defaultViewTabs = document.querySelectorAll("[data-default-tab]");
defaultViewTabs.forEach((tab) => {
tab.addEventListener("click", () => {
// Update tab styles
defaultViewTabs.forEach((t) => t.classList.remove("tab-active"));
tab.classList.add("tab-active");
// Update content visibility
const tabId = (tab as HTMLElement).dataset.defaultTab;
const views = {
events: defaultView,
settings: settingsView,
};
// Hide all views first
Object.values(views).forEach((view) => {
if (view) view.classList.add("hidden");
});
// Show the selected view
const selectedView = views[tabId as keyof typeof views];
if (selectedView) {
selectedView.classList.remove("hidden");
}
});
});
// Handle default view tab switching
const defaultViewTabs = document.querySelectorAll("[data-default-tab]");
defaultViewTabs.forEach((tab) => {
tab.addEventListener("click", () => {
// Update tab styles
defaultViewTabs.forEach((t) => t.classList.remove("tab-active"));
tab.classList.add("tab-active");
// Update content visibility
const tabId = (tab as HTMLElement).dataset.defaultTab;
const views = {
events: defaultView,
settings: settingsView,
};
// Hide all views first
Object.values(views).forEach((view) => {
if (view) view.classList.add("hidden");
});
// Show the selected view
const selectedView = views[tabId as keyof typeof views];
if (selectedView) {
selectedView.classList.remove("hidden");
}
});
});
// Add login button event listener
const loginButtons = document.querySelectorAll(".login-button");
loginButtons.forEach((button) => {
button.addEventListener("click", async () => {
try {
if (pageLoadingState)
pageLoadingState.classList.remove("hidden");
if (notAuthenticatedState)
notAuthenticatedState.classList.add("hidden");
await auth.login();
} catch (error) {
console.error("Login error:", error);
if (pageLoadingState) pageLoadingState.classList.add("hidden");
if (pageErrorState) pageErrorState.classList.remove("hidden");
}
});
// Add login button event listener
const loginButtons = document.querySelectorAll(".login-button");
loginButtons.forEach((button) => {
button.addEventListener("click", async () => {
try {
if (pageLoadingState) pageLoadingState.classList.remove("hidden");
if (notAuthenticatedState)
notAuthenticatedState.classList.add("hidden");
await auth.login();
} catch (error) {
console.error("Login error:", error);
if (pageLoadingState) pageLoadingState.classList.add("hidden");
if (pageErrorState) pageErrorState.classList.remove("hidden");
}
});
});
</script>
<style>
.hidden {
display: none !important;
}
.hidden {
display: none !important;
}
</style>