feat: update environment variables, enhance login functionality with password hashing, and improve UI components for better user experience

This commit is contained in:
吳元皓 2025-05-12 14:45:02 +08:00
parent 26d3998a70
commit 98869d5fce
10 changed files with 105 additions and 29 deletions

View File

@ -12,8 +12,7 @@ POSTGRES_URL=
GROQ_API_KEY= GROQ_API_KEY=
NUXT_PUBLIC_CLERK_PUBLISHABLE_KEY= PASSWORD_HASH_SALT=""
NUXT_CLERK_SECRET_KEY=
# SCRAPING # SCRAPING
POSTGRES_DB="" POSTGRES_DB=""

View File

@ -28,6 +28,10 @@ App Design: [Freeform](https://www.icloud.com/freeform/026AxB798cViZ9jJ2DkNsXUCQ
- Ground.news - Ground.news
- 台灣新聞 - 台灣新聞
- Threads - Threads
- xfce's Desktop Interface
- juice website
- MacOS
- Windows XP style X - UI
## Stack: ## Stack:

View File

@ -18,10 +18,12 @@
"@uploadthing/nuxt": "^7.1.7", "@uploadthing/nuxt": "^7.1.7",
"@vueuse/core": "^13.1.0", "@vueuse/core": "^13.1.0",
"animate.css": "^4.1.1", "animate.css": "^4.1.1",
"argon2": "^0.43.0",
"bootstrap-icons": "^1.12.1", "bootstrap-icons": "^1.12.1",
"class-variance-authority": "^0.7.1", "class-variance-authority": "^0.7.1",
"clsx": "^2.1.1", "clsx": "^2.1.1",
"crypto-js": "^4.2.0", "crypto-js": "^4.2.0",
"emoji-js": "^3.8.1",
"groq-sdk": "^0.21.0", "groq-sdk": "^0.21.0",
"gsap": "^3.13.0", "gsap": "^3.13.0",
"html-to-json-parser": "^2.0.1", "html-to-json-parser": "^2.0.1",
@ -460,6 +462,8 @@
"@parcel/watcher-win32-x64": ["@parcel/watcher-win32-x64@2.5.1", "", { "os": "win32", "cpu": "x64" }, "sha512-9lHBdJITeNR++EvSQVUcaZoWupyHfXe1jZvGZ06O/5MflPcuPLtEphScIBL+AiCWBO46tDSHzWyD0uDmmZqsgA=="], "@parcel/watcher-win32-x64": ["@parcel/watcher-win32-x64@2.5.1", "", { "os": "win32", "cpu": "x64" }, "sha512-9lHBdJITeNR++EvSQVUcaZoWupyHfXe1jZvGZ06O/5MflPcuPLtEphScIBL+AiCWBO46tDSHzWyD0uDmmZqsgA=="],
"@phc/format": ["@phc/format@1.0.0", "", {}, "sha512-m7X9U6BG2+J+R1lSOdCiITLLrxm+cWlNI3HUFA92oLO77ObGNzaKdh8pMLqdZcshtkKuV84olNNXDfMc4FezBQ=="],
"@pkgjs/parseargs": ["@pkgjs/parseargs@0.11.0", "", {}, "sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg=="], "@pkgjs/parseargs": ["@pkgjs/parseargs@0.11.0", "", {}, "sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg=="],
"@polka/url": ["@polka/url@1.0.0-next.29", "", {}, "sha512-wwQAWhWSuHaag8c4q/KN/vCoeOJYshAIvMQwD4GpSb3OiZklFfvAgmj0VCBBImRpuF/aFgIRzllXlVX93Jevww=="], "@polka/url": ["@polka/url@1.0.0-next.29", "", {}, "sha512-wwQAWhWSuHaag8c4q/KN/vCoeOJYshAIvMQwD4GpSb3OiZklFfvAgmj0VCBBImRpuF/aFgIRzllXlVX93Jevww=="],
@ -812,6 +816,8 @@
"arg": ["arg@5.0.2", "", {}, "sha512-PYjyFOLKQ9y57JvQ6QLo8dAgNqswh8M1RMJYdQduT6xbWSgK36P/Z/v+p888pM69jMMfS8Xd8F6I1kQ/I9HUGg=="], "arg": ["arg@5.0.2", "", {}, "sha512-PYjyFOLKQ9y57JvQ6QLo8dAgNqswh8M1RMJYdQduT6xbWSgK36P/Z/v+p888pM69jMMfS8Xd8F6I1kQ/I9HUGg=="],
"argon2": ["argon2@0.43.0", "", { "dependencies": { "@phc/format": "^1.0.0", "node-addon-api": "^8.3.1", "node-gyp-build": "^4.8.4" } }, "sha512-u/HKLcbWShVDhkfwI4hWyiUf3qyX8QhTfaIv2cWE18uqhXCmR5hb6Ed7oqYi2KCQegeAnRhiFzbjzm7i5yl1GA=="],
"argparse": ["argparse@2.0.1", "", {}, "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q=="], "argparse": ["argparse@2.0.1", "", {}, "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q=="],
"aria-hidden": ["aria-hidden@1.2.4", "", { "dependencies": { "tslib": "^2.0.0" } }, "sha512-y+CcFFwelSXpLZk/7fMB2mUbGtX9lKycf1MWJ7CaTIERyitVlyQx6C+sxcROU2BAJ24OiZyK+8wj2i8AlBoS3A=="], "aria-hidden": ["aria-hidden@1.2.4", "", { "dependencies": { "tslib": "^2.0.0" } }, "sha512-y+CcFFwelSXpLZk/7fMB2mUbGtX9lKycf1MWJ7CaTIERyitVlyQx6C+sxcROU2BAJ24OiZyK+8wj2i8AlBoS3A=="],
@ -1118,6 +1124,10 @@
"electron-to-chromium": ["electron-to-chromium@1.5.151", "", {}, "sha512-Rl6uugut2l9sLojjS4H4SAr3A4IgACMLgpuEMPYCVcKydzfyPrn5absNRju38IhQOf/NwjJY8OGWjlteqYeBCA=="], "electron-to-chromium": ["electron-to-chromium@1.5.151", "", {}, "sha512-Rl6uugut2l9sLojjS4H4SAr3A4IgACMLgpuEMPYCVcKydzfyPrn5absNRju38IhQOf/NwjJY8OGWjlteqYeBCA=="],
"emoji-datasource": ["emoji-datasource@15.0.1", "", {}, "sha512-aF5Q6LCKXzJzpG4K0ETiItuzz0xLYxNexR9qWw45/shuuEDWZkOIbeGHA23uopOSYA/LmeZIXIFsySCx+YKg2g=="],
"emoji-js": ["emoji-js@3.8.1", "", { "dependencies": { "emoji-datasource": "15.0.1" } }, "sha512-yyXMnZLXgqQHAhEm2DKK4Nrca+jbLQfNOP2mLcNTS6XxzzbQLDFHAguPQrtJS4Udot0Pvomwmh1ckQzhrePhKw=="],
"emoji-regex": ["emoji-regex@10.4.0", "", {}, "sha512-EC+0oUMY1Rqm4O6LLrgjtYDvcVYTy7chDnM4Q7030tP4Kwj3u/pR6gP9ygnp2CJMK5Gq+9Q2oqmrFJAz01DXjw=="], "emoji-regex": ["emoji-regex@10.4.0", "", {}, "sha512-EC+0oUMY1Rqm4O6LLrgjtYDvcVYTy7chDnM4Q7030tP4Kwj3u/pR6gP9ygnp2CJMK5Gq+9Q2oqmrFJAz01DXjw=="],
"enabled": ["enabled@2.0.0", "", {}, "sha512-AKrN98kuwOzMIdAizXGI86UFBoo26CL21UM763y1h/GMSJ4/OHU9k2YlsmBpyScFo/wbLzWQJBMCW4+IO3/+OQ=="], "enabled": ["enabled@2.0.0", "", {}, "sha512-AKrN98kuwOzMIdAizXGI86UFBoo26CL21UM763y1h/GMSJ4/OHU9k2YlsmBpyScFo/wbLzWQJBMCW4+IO3/+OQ=="],
@ -1366,6 +1376,8 @@
"iconv-lite": ["iconv-lite@0.6.3", "", { "dependencies": { "safer-buffer": ">= 2.1.2 < 3.0.0" } }, "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw=="], "iconv-lite": ["iconv-lite@0.6.3", "", { "dependencies": { "safer-buffer": ">= 2.1.2 < 3.0.0" } }, "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw=="],
"idb-keyval": ["idb-keyval@5.1.5", "", { "dependencies": { "safari-14-idb-fix": "^1.0.6" } }, "sha512-J1utxYWQokYjy01LvDQ7WmiAtZCGUSkVi9EIBfUSyLOr/BesnMIxNGASTh9A1LzeISSjSqEPsfFdTss7EE7ofQ=="],
"ieee754": ["ieee754@1.2.1", "", {}, "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA=="], "ieee754": ["ieee754@1.2.1", "", {}, "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA=="],
"ignore": ["ignore@7.0.4", "", {}, "sha512-gJzzk+PQNznz8ysRrC0aOkBNVRBDtE1n53IqyqEf3PXrYwomFs5q4pGMizBMJF+ykh03insJ27hB8gSrD2Hn8A=="], "ignore": ["ignore@7.0.4", "", {}, "sha512-gJzzk+PQNznz8ysRrC0aOkBNVRBDtE1n53IqyqEf3PXrYwomFs5q4pGMizBMJF+ykh03insJ27hB8gSrD2Hn8A=="],
@ -1674,7 +1686,7 @@
"node-abi": ["node-abi@3.75.0", "", { "dependencies": { "semver": "^7.3.5" } }, "sha512-OhYaY5sDsIka7H7AtijtI9jwGYLyl29eQn/W623DiN/MIv5sUqc4g7BIDThX+gb7di9f6xK02nkp8sdfFWZLTg=="], "node-abi": ["node-abi@3.75.0", "", { "dependencies": { "semver": "^7.3.5" } }, "sha512-OhYaY5sDsIka7H7AtijtI9jwGYLyl29eQn/W623DiN/MIv5sUqc4g7BIDThX+gb7di9f6xK02nkp8sdfFWZLTg=="],
"node-addon-api": ["node-addon-api@7.1.1", "", {}, "sha512-5m3bsyrjFWE1xf7nz7YXdN4udnVtXK6/Yfgn5qnahL6bCkf2yKt4k3nuTKAtT4r3IG8JNR2ncsIMdZuAzJjHQQ=="], "node-addon-api": ["node-addon-api@8.3.1", "", {}, "sha512-lytcDEdxKjGJPTLEfW4mYMigRezMlyJY8W4wxJK8zE533Jlb8L8dRuObJFWg2P+AuOIxoCgKF+2Oq4d4Zd0OUA=="],
"node-domexception": ["node-domexception@1.0.0", "", {}, "sha512-/jKZoMpw0F8GRwl4/eLROPA3cfcXtLApP0QzLmUT/HuPCZWyB7IY9ZrMeKw2O/nFIqPQB3PVM9aYm0F312AXDQ=="], "node-domexception": ["node-domexception@1.0.0", "", {}, "sha512-/jKZoMpw0F8GRwl4/eLROPA3cfcXtLApP0QzLmUT/HuPCZWyB7IY9ZrMeKw2O/nFIqPQB3PVM9aYm0F312AXDQ=="],
@ -2018,6 +2030,8 @@
"run-parallel": ["run-parallel@1.2.0", "", { "dependencies": { "queue-microtask": "^1.2.2" } }, "sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA=="], "run-parallel": ["run-parallel@1.2.0", "", { "dependencies": { "queue-microtask": "^1.2.2" } }, "sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA=="],
"safari-14-idb-fix": ["safari-14-idb-fix@1.0.6", "", {}, "sha512-oTEQOdMwRX+uCtWCKT1nx2gAeSdpr8elg/2gcaKUH00SJU2xWESfkx11nmXwTRHy7xfQoj1o4TTQvdmuBosTnA=="],
"safe-buffer": ["safe-buffer@5.2.1", "", {}, "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ=="], "safe-buffer": ["safe-buffer@5.2.1", "", {}, "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ=="],
"safe-regex-test": ["safe-regex-test@1.1.0", "", { "dependencies": { "call-bound": "^1.0.2", "es-errors": "^1.3.0", "is-regex": "^1.2.1" } }, "sha512-x/+Cz4YrimQxQccJf5mKEbIa1NzeCRNI5Ecl/ekmlYaampdNLPalVyIcCZNNH3MvmqBugV5TMYZXv0ljslUlaw=="], "safe-regex-test": ["safe-regex-test@1.1.0", "", { "dependencies": { "call-bound": "^1.0.2", "es-errors": "^1.3.0", "is-regex": "^1.2.1" } }, "sha512-x/+Cz4YrimQxQccJf5mKEbIa1NzeCRNI5Ecl/ekmlYaampdNLPalVyIcCZNNH3MvmqBugV5TMYZXv0ljslUlaw=="],
@ -2466,6 +2480,8 @@
"@parcel/watcher/detect-libc": ["detect-libc@1.0.3", "", { "bin": { "detect-libc": "./bin/detect-libc.js" } }, "sha512-pGjwhsmsp4kL2RTz08wcOlGN83otlqHeD/Z5T8GXZB+/YcpQ/dgo+lbU8ZsGxV0HIvqqxo9l7mqYwyYMD9bKDg=="], "@parcel/watcher/detect-libc": ["detect-libc@1.0.3", "", { "bin": { "detect-libc": "./bin/detect-libc.js" } }, "sha512-pGjwhsmsp4kL2RTz08wcOlGN83otlqHeD/Z5T8GXZB+/YcpQ/dgo+lbU8ZsGxV0HIvqqxo9l7mqYwyYMD9bKDg=="],
"@parcel/watcher/node-addon-api": ["node-addon-api@7.1.1", "", {}, "sha512-5m3bsyrjFWE1xf7nz7YXdN4udnVtXK6/Yfgn5qnahL6bCkf2yKt4k3nuTKAtT4r3IG8JNR2ncsIMdZuAzJjHQQ=="],
"@parcel/watcher-wasm/napi-wasm": ["napi-wasm@1.1.3", "", { "bundled": true }, "sha512-h/4nMGsHjZDCYmQVNODIrYACVJ+I9KItbG+0si6W/jSjdA9JbWDoU4LLeMXVcEQGHjttI2tuXqDrbGF7qkUHHg=="], "@parcel/watcher-wasm/napi-wasm": ["napi-wasm@1.1.3", "", { "bundled": true }, "sha512-h/4nMGsHjZDCYmQVNODIrYACVJ+I9KItbG+0si6W/jSjdA9JbWDoU4LLeMXVcEQGHjttI2tuXqDrbGF7qkUHHg=="],
"@poppinss/colors/kleur": ["kleur@4.1.5", "", {}, "sha512-o+NO+8WrRiQEE4/7nwRJhN1HWpVmJm511pBHUxPLtp0BUISzlBplORYSmTclCnJvQq2tKu/sgl3xVpkc7ZWuQQ=="], "@poppinss/colors/kleur": ["kleur@4.1.5", "", {}, "sha512-o+NO+8WrRiQEE4/7nwRJhN1HWpVmJm511pBHUxPLtp0BUISzlBplORYSmTclCnJvQq2tKu/sgl3xVpkc7ZWuQQ=="],

View File

@ -13,9 +13,10 @@ const emit = defineEmits(["close"]);
const isDragging = ref(false); const isDragging = ref(false);
const position = ref({ const position = ref({
x: props.initialX || Math.floor(window.innerWidth / 2) - Math.random() * 200, x: props.initialX || Math.floor(window.innerWidth / 2 - (parseInt(props.width || '400') / 2)),
y: props.initialY || Math.floor(window.innerHeight / 2) - Math.random() * 10, y: props.initialY || Math.floor(window.innerHeight / 2 - (parseInt(props.height || '300') / 2)),
}); });
const offset = ref({ x: 0, y: 0 }); const offset = ref({ x: 0, y: 0 });
const doDrag = useThrottleFn((e: MouseEvent) => { const doDrag = useThrottleFn((e: MouseEvent) => {

View File

@ -14,7 +14,7 @@ const submitUserPassword = async () => {
}, },
body: JSON.stringify({ body: JSON.stringify({
username: userAccount.value, username: userAccount.value,
pcssword: password, password: password,
}), }),
}) })
const res = await sendData.json(); const res = await sendData.json();
@ -45,7 +45,7 @@ const submitUserPassword = async () => {
<input <input
type="text" type="text"
placeholder="Your Email" placeholder="Username"
class="mb-2 p-2 border rounded" class="mb-2 p-2 border rounded"
v-model="userAccount" v-model="userAccount"
/> />

View File

@ -13,7 +13,7 @@ const { data: source, pending, error } = await useFetch("/api/getData/fetchSourc
</script> </script>
<template> <template>
<div > <div >
<div v-for="item in source.data" :key="item.id"> <div v-for="item in source?.data" :key="item.id">
<h1>{{ item.title }}</h1> <h1>{{ item.title }}</h1>
<span>{{ item.description }}</span> <span>{{ item.description }}</span>
<a :href="item.url">{{ item.url }}</a> <a :href="item.url">{{ item.url }}</a>

View File

@ -27,10 +27,12 @@
"@uploadthing/nuxt": "^7.1.7", "@uploadthing/nuxt": "^7.1.7",
"@vueuse/core": "^13.1.0", "@vueuse/core": "^13.1.0",
"animate.css": "^4.1.1", "animate.css": "^4.1.1",
"argon2": "^0.43.0",
"bootstrap-icons": "^1.12.1", "bootstrap-icons": "^1.12.1",
"class-variance-authority": "^0.7.1", "class-variance-authority": "^0.7.1",
"clsx": "^2.1.1", "clsx": "^2.1.1",
"crypto-js": "^4.2.0", "crypto-js": "^4.2.0",
"emoji-js": "^3.8.1",
"groq-sdk": "^0.21.0", "groq-sdk": "^0.21.0",
"gsap": "^3.13.0", "gsap": "^3.13.0",
"html-to-json-parser": "^2.0.1", "html-to-json-parser": "^2.0.1",

View File

@ -123,21 +123,20 @@ const toggleLangMenu = () => {
// values // values
const activeWindows = ref<associAppWindowInterface>([]); const activeWindows = ref<associAppWindowInterface>([]);
const currentApplication = ref();
// ?opemapp= component // ?opemapp= component
const openApp = ref(false); const openApp = ref(false);
const openAppId = ref(); const openAppId = ref();
watch(() => route.query.openapp, (newVal) => { const openAppNameQuery = ref();
if (newVal) {
openApp.value = true; onMounted(() => {
openAppId.value = newVal; openApp.value = route.query.openapp;
router.replace({ openAppId.value = route.query.id;
path: route.path, openAppNameQuery.value = route.query.name;
query: {}, if (openApp.value) {
}); openWindow(openApp.value);
} }
}); })
const associAppWindow = [ const associAppWindow = [
{ {
@ -149,9 +148,11 @@ const associAppWindow = [
height: "500px", height: "500px",
}, },
{ name: "login", id: "2", title: t("app.login") , component: LoginWindow }, { name: "login", id: "2", title: t("app.login") , component: LoginWindow },
{ name: "sources", id: "3", title: t("app.sources"), component: SourcesWindow }, { name: "sources", id: "3", title: t("app.sources"), component: SourcesWindow }
]; ];
const currentOpenAppId = ref(0);
const findAndOpenWindow = (windowName: string) => { const findAndOpenWindow = (windowName: string) => {
const app = associAppWindow.find((app) => app.name === windowName) const app = associAppWindow.find((app) => app.name === windowName)
@ -166,13 +167,14 @@ const findAndOpenWindow = (windowName: string) => {
const windowComponent = shallowRef(app.component) const windowComponent = shallowRef(app.component)
activeWindows.value.push({ activeWindows.value.push({
id: `${windowName}-${Date.now()}`, id: currentOpenAppId.value,
component: windowComponent, component: windowComponent,
name: windowName, name: windowName,
title: app.title, title: app.title,
width: app.width || "400px", width: app.width || "400px",
height: app.height || "300px", height: app.height || "300px",
}) })
currentOpenAppId.value++;
} }
} }
@ -183,8 +185,18 @@ const closeWindow = (windowId: string) => {
console.log("activeWindows.value", activeWindows.value); console.log("activeWindows.value", activeWindows.value);
}; };
const topWindow = (windowId: string) => {
const windowIndex = activeWindows.value.findIndex(
(window) => window.id === windowId,
);
if (windowIndex !== -1) {
const [window] = activeWindows.value.splice(windowIndex, 1);
activeWindows.value.push(window);
}
};
useSeoMeta({ useSeoMeta({
title: currentApplication.value.title + " - " + t("app.title"), title: "hi" + " - Desktop",
}) })
</script> </script>
<template> <template>
@ -249,17 +261,19 @@ useSeoMeta({
</Transition> </Transition>
<!--Main desktop contents--> <!--Main desktop contents-->
<div <div
class="flex flex-col justify-center align-center text-center absolute w-full h-screen inset-x-0 inset-y-0 z-[-1]" class="flex flex-col justify-center align-center text-center absolute w-full h-screen inset-x-0 inset-y-0 z-[-10]"
id="desktop" id="desktop"
></div> ></div>
<div> <Transition>
<DraggableWindow <div>
<DraggableWindow
v-for="window in activeWindows" v-for="window in activeWindows"
:key="window.id" :key="window.id"
:title="window.title" :title="window.title"
@close="closeWindow(window.id)" @close="closeWindow(window.id)"
:width="window.width" :width="window.width"
:height="window.height" :height="window.height"
@clicked="topWindow(window.id)"
> >
<Suspense> <Suspense>
<Component <Component
@ -268,10 +282,11 @@ useSeoMeta({
/> />
</Suspense> </Suspense>
</DraggableWindow> </DraggableWindow>
</div> </div>
</Transition>
<!--Footer--> <!--Footer-->
<div <div
class="absolute w-[calc(100% - 5px)] inset-x-0 bottom-0 mx-[1.5px] p-3 justify-between align-center flex flex-row" class="absolute w-[calc(100% - 5px)] inset-x-0 bottom-0 mx-[1.5px] p-3 justify-between align-center flex flex-row z-0"
> >
<div class=""> <div class="">
<!--Lang--> <!--Lang-->

View File

@ -1,4 +1,5 @@
<script setup lang="ts"> <script setup lang="ts">
import EmojiConvertor from "emoji-js";
import { gsap } from "gsap"; import { gsap } from "gsap";
import { TextPlugin } from "gsap/TextPlugin"; import { TextPlugin } from "gsap/TextPlugin";
gsap.registerPlugin(TextPlugin); gsap.registerPlugin(TextPlugin);
@ -15,6 +16,8 @@ const messages = [
"BlindSpec", "BlindSpec",
]; ];
const emoji = new EmojiConvertor();
onMounted(() => { onMounted(() => {
const tl = gsap.timeline({ repeat: -1 }); const tl = gsap.timeline({ repeat: -1 });
messages.forEach((message) => { messages.forEach((message) => {
@ -76,7 +79,7 @@ onMounted(() => {
<div <div
class="flex flex-col justify-center items-center align-middle bg-[#C9C9C9]/60 rounded-xl shadow-lg p-5 m-5 w-[300px] h-[200px]" class="flex flex-col justify-center items-center align-middle bg-[#C9C9C9]/60 rounded-xl shadow-lg p-5 m-5 w-[300px] h-[200px]"
> >
<h1 class="text-8xl mt-0">🤔</h1> <h1 class="text-8xl mt-0">{{ emoji.replace_colons(':thinking_face:') }}</h1>
<h2 class="text-xl font-bold">Why?</h2> <h2 class="text-xl font-bold">Why?</h2>
<span class="text-sm" <span class="text-sm"
>{{ t("home.whydes")}}</span >{{ t("home.whydes")}}</span
@ -85,7 +88,7 @@ onMounted(() => {
<div <div
class="flex flex-col justify-center items-center align-middle bg-[#C9C9C9]/60 rounded-xl shadow-lg p-5 m-5 w-[300px] h-[200px]" class="flex flex-col justify-center items-center align-middle bg-[#C9C9C9]/60 rounded-xl shadow-lg p-5 m-5 w-[300px] h-[200px]"
> >
<h1 class="text-8xl mt-0">🧐</h1> <h1 class="text-8xl mt-0">{{ emoji.replace_colons(':face_with_monocle:') }}</h1>
<h2 class="text-xl font-bold">How?</h2> <h2 class="text-xl font-bold">How?</h2>
<span class="text-sm" <span class="text-sm"
>{{ t("home.howdes")}}</span >{{ t("home.howdes")}}</span

36
server/api/user/login.ts Normal file
View File

@ -0,0 +1,36 @@
import sql from "~/server/components/postgres";
import argon2 from "argon2";
export default defineEventHandler(async (event) => {
const salt = process.env.PASSWORD_HASH_SALT;
if (!salt) {
throw createError({
statusCode: 500,
message: 'Internal server error'
});
}
const body = await readBody(event);
const { username, password } = body;
if (!username || !password) {
throw createError({
statusCode: 400,
message: 'Username and password are required'
});
}
const USERNAME_PATTERN = /^[a-zA-Z0-9_]{3,20}$/;
if (!USERNAME_PATTERN.test(username)) {
throw createError({
statusCode: 400,
message: 'Invalid username.'
});
}
// Server side hashing
const hashedPassword = await argon2.hash(salt, password);
// Check if user exists, if not, create a user
try {
console.log(username);
console.log(hashedPassword);
} catch (e) {
}
})