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=
NUXT_PUBLIC_CLERK_PUBLISHABLE_KEY=
NUXT_CLERK_SECRET_KEY=
PASSWORD_HASH_SALT=""
# SCRAPING
POSTGRES_DB=""

View File

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

View File

@ -18,10 +18,12 @@
"@uploadthing/nuxt": "^7.1.7",
"@vueuse/core": "^13.1.0",
"animate.css": "^4.1.1",
"argon2": "^0.43.0",
"bootstrap-icons": "^1.12.1",
"class-variance-authority": "^0.7.1",
"clsx": "^2.1.1",
"crypto-js": "^4.2.0",
"emoji-js": "^3.8.1",
"groq-sdk": "^0.21.0",
"gsap": "^3.13.0",
"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=="],
"@phc/format": ["@phc/format@1.0.0", "", {}, "sha512-m7X9U6BG2+J+R1lSOdCiITLLrxm+cWlNI3HUFA92oLO77ObGNzaKdh8pMLqdZcshtkKuV84olNNXDfMc4FezBQ=="],
"@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=="],
@ -812,6 +816,8 @@
"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=="],
"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=="],
"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=="],
"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=="],
"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=="],
"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-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=="],
@ -2018,6 +2030,8 @@
"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-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/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=="],
"@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 position = ref({
x: props.initialX || Math.floor(window.innerWidth / 2) - Math.random() * 200,
y: props.initialY || Math.floor(window.innerHeight / 2) - Math.random() * 10,
x: props.initialX || Math.floor(window.innerWidth / 2 - (parseInt(props.width || '400') / 2)),
y: props.initialY || Math.floor(window.innerHeight / 2 - (parseInt(props.height || '300') / 2)),
});
const offset = ref({ x: 0, y: 0 });
const doDrag = useThrottleFn((e: MouseEvent) => {

View File

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

View File

@ -13,7 +13,7 @@ const { data: source, pending, error } = await useFetch("/api/getData/fetchSourc
</script>
<template>
<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>
<span>{{ item.description }}</span>
<a :href="item.url">{{ item.url }}</a>

View File

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

View File

@ -123,21 +123,20 @@ const toggleLangMenu = () => {
// values
const activeWindows = ref<associAppWindowInterface>([]);
const currentApplication = ref();
// ?opemapp= component
const openApp = ref(false);
const openAppId = ref();
watch(() => route.query.openapp, (newVal) => {
if (newVal) {
openApp.value = true;
openAppId.value = newVal;
router.replace({
path: route.path,
query: {},
});
const openAppNameQuery = ref();
onMounted(() => {
openApp.value = route.query.openapp;
openAppId.value = route.query.id;
openAppNameQuery.value = route.query.name;
if (openApp.value) {
openWindow(openApp.value);
}
});
})
const associAppWindow = [
{
@ -149,9 +148,11 @@ const associAppWindow = [
height: "500px",
},
{ 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 app = associAppWindow.find((app) => app.name === windowName)
@ -166,13 +167,14 @@ const findAndOpenWindow = (windowName: string) => {
const windowComponent = shallowRef(app.component)
activeWindows.value.push({
id: `${windowName}-${Date.now()}`,
id: currentOpenAppId.value,
component: windowComponent,
name: windowName,
title: app.title,
width: app.width || "400px",
height: app.height || "300px",
})
currentOpenAppId.value++;
}
}
@ -183,8 +185,18 @@ const closeWindow = (windowId: string) => {
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({
title: currentApplication.value.title + " - " + t("app.title"),
title: "hi" + " - Desktop",
})
</script>
<template>
@ -249,9 +261,10 @@ useSeoMeta({
</Transition>
<!--Main desktop contents-->
<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"
></div>
<Transition>
<div>
<DraggableWindow
v-for="window in activeWindows"
@ -260,6 +273,7 @@ useSeoMeta({
@close="closeWindow(window.id)"
:width="window.width"
:height="window.height"
@clicked="topWindow(window.id)"
>
<Suspense>
<Component
@ -269,9 +283,10 @@ useSeoMeta({
</Suspense>
</DraggableWindow>
</div>
</Transition>
<!--Footer-->
<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="">
<!--Lang-->

View File

@ -1,4 +1,5 @@
<script setup lang="ts">
import EmojiConvertor from "emoji-js";
import { gsap } from "gsap";
import { TextPlugin } from "gsap/TextPlugin";
gsap.registerPlugin(TextPlugin);
@ -15,6 +16,8 @@ const messages = [
"BlindSpec",
];
const emoji = new EmojiConvertor();
onMounted(() => {
const tl = gsap.timeline({ repeat: -1 });
messages.forEach((message) => {
@ -76,7 +79,7 @@ onMounted(() => {
<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]"
>
<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>
<span class="text-sm"
>{{ t("home.whydes")}}</span
@ -85,7 +88,7 @@ onMounted(() => {
<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]"
>
<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>
<span class="text-sm"
>{{ 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) {
}
})