1
0

working nhost client

هذا الالتزام موجود في:
Your Name
2025-10-08 09:29:32 +00:00
الأصل b23bc554c5
التزام a837154231
10 ملفات معدلة مع 1189 إضافات و663 حذوفات

46
.dockerignore Normal file
عرض الملف

@@ -0,0 +1,46 @@
# التبعيات
node_modules
npm-debug.log*
yarn-debug.log*
yarn-error.log*
# ملفات البيئة
.env
.env.local
.env.development.local
.env.test.local
.env.production.local
# ملفات السجلات
logs
*.log
# ملفات التخزين المؤقت
.cache
.parcel-cache
.nuxt
.next
dist
build
# نظام التحكم بالنسخ
.git
.gitignore
README.md
# ملفات النظام
.DS_Store
Thumbs.db
# ملفات IDE
.vscode
.idea
*.swp
*.swo
# ملفات الاختبار
coverage
.nyc_output
node_modules

17
.ghaymah.json Normal file
عرض الملف

@@ -0,0 +1,17 @@
{
"id": "d7d9b033-eb33-4dcd-93f7-aa6f03ed706b",
"name": "dashboard",
"projectId": "2f99d8bc-53ba-44fc-ac40-2ff3dc380c87",
"ports": [
{
"expose": true,
"number": 5173
}
],
"publicAccess": {
"enabled": true,
"domain": "auto"
},
"resourceTier": "t5",
"dockerFileName": "Dockerfile"
}

15
Dockerfile Normal file
عرض الملف

@@ -0,0 +1,15 @@
FROM node:20-alpine
WORKDIR /app
COPY package.json .
RUN npm install
COPY . .
ENV PORT=8080
EXPOSE 8080
CMD [ "npm", "run", "dev"]

عرض الملف

@@ -4,7 +4,7 @@
<meta charset="UTF-8" /> <meta charset="UTF-8" />
<link rel="icon" type="image/svg+xml" href="/vite.svg" /> <link rel="icon" type="image/svg+xml" href="/vite.svg" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" /> <meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Vite + React + TS</title> <title>Onekeyword</title>
</head> </head>
<body> <body>
<div id="root"></div> <div id="root"></div>

423
package-lock.json مولّد
عرض الملف

@@ -8,6 +8,10 @@
"name": "my-app", "name": "my-app",
"version": "0.0.0", "version": "0.0.0",
"dependencies": { "dependencies": {
"@apollo/client": "^4.0.7",
"@nhost/react": "^3.11.2",
"graphql": "^16.11.0",
"graphql-ws": "^6.0.6",
"lucide-react": "^0.544.0", "lucide-react": "^0.544.0",
"react": "^19.1.1", "react": "^19.1.1",
"react-dom": "^19.1.1", "react-dom": "^19.1.1",
@@ -44,6 +48,47 @@
"url": "https://github.com/sponsors/sindresorhus" "url": "https://github.com/sponsors/sindresorhus"
} }
}, },
"node_modules/@apollo/client": {
"version": "4.0.7",
"resolved": "https://registry.npmjs.org/@apollo/client/-/client-4.0.7.tgz",
"integrity": "sha512-hZp/mKtAqM+g6buUnu6Wqtyc33QebvfdY0SE46xWea4lU1CxwI57VORy2N2vA9CoCRgYM4ELNXzr6nNErAdhfg==",
"workspaces": [
"dist",
"codegen",
"scripts/codemods/ac3-to-ac4"
],
"dependencies": {
"@graphql-typed-document-node/core": "^3.1.1",
"@wry/caches": "^1.0.0",
"@wry/equality": "^0.5.6",
"@wry/trie": "^0.5.0",
"graphql-tag": "^2.12.6",
"optimism": "^0.18.0",
"tslib": "^2.3.0"
},
"peerDependencies": {
"graphql": "^16.0.0",
"graphql-ws": "^5.5.5 || ^6.0.3",
"react": "^17.0.0 || ^18.0.0 || >=19.0.0-rc",
"react-dom": "^17.0.0 || ^18.0.0 || >=19.0.0-rc",
"rxjs": "^7.3.0",
"subscriptions-transport-ws": "^0.9.0 || ^0.11.0"
},
"peerDependenciesMeta": {
"graphql-ws": {
"optional": true
},
"react": {
"optional": true
},
"react-dom": {
"optional": true
},
"subscriptions-transport-ws": {
"optional": true
}
}
},
"node_modules/@babel/code-frame": { "node_modules/@babel/code-frame": {
"version": "7.27.1", "version": "7.27.1",
"resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.27.1.tgz", "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.27.1.tgz",
@@ -922,6 +967,14 @@
"node": "^18.18.0 || ^20.9.0 || >=21.1.0" "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
} }
}, },
"node_modules/@graphql-typed-document-node/core": {
"version": "3.2.0",
"resolved": "https://registry.npmjs.org/@graphql-typed-document-node/core/-/core-3.2.0.tgz",
"integrity": "sha512-mB9oAsNCm9aM3/SOv4YtBMqZbYj10R7dkq8byBqxGY/ncFwhf2oQzMV+LCRlWoDSEBJ3COiR1yeDvMtsoOsuFQ==",
"peerDependencies": {
"graphql": "^0.8.0 || ^0.9.0 || ^0.10.0 || ^0.11.0 || ^0.12.0 || ^0.13.0 || ^14.0.0 || ^15.0.0 || ^16.0.0 || ^17.0.0"
}
},
"node_modules/@humanfs/core": { "node_modules/@humanfs/core": {
"version": "0.19.1", "version": "0.19.1",
"resolved": "https://registry.npmjs.org/@humanfs/core/-/core-0.19.1.tgz", "resolved": "https://registry.npmjs.org/@humanfs/core/-/core-0.19.1.tgz",
@@ -1042,6 +1095,126 @@
"@jridgewell/sourcemap-codec": "^1.4.14" "@jridgewell/sourcemap-codec": "^1.4.14"
} }
}, },
"node_modules/@nhost/graphql-js": {
"version": "0.3.0",
"resolved": "https://registry.npmjs.org/@nhost/graphql-js/-/graphql-js-0.3.0.tgz",
"integrity": "sha512-CVYq6wx0VbaYdpUBmfNO/6mZatHB5+YBCqFjWyxhpN1nzHCHEO6rgdL7j0qk31OFE6XAX0z7AQZSXg1Pn63GUw==",
"deprecated": "⚠ DEPRECATED: This package is deprecated in favor of @nhost/nhost-js@^4.0.0. The new SDK is auto-generated from OpenAPI specs, provides better consistency, and works isomorphically across all JavaScript environments. This package will receive security patches and bug fixes until March 31st but no new features. Migrate to @nhost/nhost-js for future compatibility. Learn more: https://nhost.io/blog/introducing-new-javascript-sdk",
"dependencies": {
"@graphql-typed-document-node/core": "^3.2.0",
"base-64": "^1.0.0",
"isomorphic-unfetch": "^3.1.0",
"jwt-decode": "^4.0.0"
},
"peerDependencies": {
"graphql": "^14.0.0 || ^15.0.0 || ^16.0.0"
}
},
"node_modules/@nhost/hasura-auth-js": {
"version": "2.12.0",
"resolved": "https://registry.npmjs.org/@nhost/hasura-auth-js/-/hasura-auth-js-2.12.0.tgz",
"integrity": "sha512-yOugrb9VCJr7Br/aSmv3iSN0rHvSBmynWppD1jiWuGqGVE0Fe/nwmYzJka1+5xDEdjtJvqaLnINEd84FI+pXSQ==",
"deprecated": "⚠ DEPRECATED: This package is deprecated in favor of @nhost/nhost-js@^4.0.0. The new SDK is auto-generated from OpenAPI specs, provides better consistency, and works isomorphically across all JavaScript environments. This package will receive security patches and bug fixes until March 31st but no new features. Migrate to @nhost/nhost-js for future compatibility. Learn more: https://nhost.io/blog/introducing-new-javascript-sdk",
"dependencies": {
"@simplewebauthn/browser": "^9.0.1",
"fetch-ponyfill": "^7.1.0",
"js-cookie": "^3.0.5",
"jwt-decode": "^4.0.0",
"xstate": "^4.38.3"
}
},
"node_modules/@nhost/hasura-storage-js": {
"version": "2.9.0",
"resolved": "https://registry.npmjs.org/@nhost/hasura-storage-js/-/hasura-storage-js-2.9.0.tgz",
"integrity": "sha512-FCeQpiqxH9JAAMwsS5kEv0OwyN8WhSD65XYM3P8CAeOqbun8yreQno7/wovFsGPVZqMgFDJ0VG9RhR1MFsPqdA==",
"deprecated": "⚠ DEPRECATED: This package is deprecated in favor of @nhost/nhost-js@^4.0.0. The new SDK is auto-generated from OpenAPI specs, provides better consistency, and works isomorphically across all JavaScript environments. This package will receive security patches and bug fixes until March 31st but no new features. Migrate to @nhost/nhost-js for future compatibility. Learn more: https://nhost.io/blog/introducing-new-javascript-sdk",
"dependencies": {
"graphql": "16.8.1",
"xstate": "^4.38.3"
}
},
"node_modules/@nhost/hasura-storage-js/node_modules/graphql": {
"version": "16.8.1",
"resolved": "https://registry.npmjs.org/graphql/-/graphql-16.8.1.tgz",
"integrity": "sha512-59LZHPdGZVh695Ud9lRzPBVTtlX9ZCV150Er2W43ro37wVof0ctenSaskPPjN7lVTIN8mSZt8PHUNKZuNQUuxw==",
"engines": {
"node": "^12.22.0 || ^14.16.0 || ^16.0.0 || >=17.0.0"
}
},
"node_modules/@nhost/nhost-js": {
"version": "3.3.1",
"resolved": "https://registry.npmjs.org/@nhost/nhost-js/-/nhost-js-3.3.1.tgz",
"integrity": "sha512-AzOUalIQr0BS2psgEqxI+AMbNjSKB5RUOGzZX82U7YVcbJ9YvYf6a25EmtnJpItmN7H/h22Vxu8AbXqdtsGVkQ==",
"deprecated": "⚠ DEPRECATED: This version is deprecated in favor of @nhost/nhost-js@^4.0.0. The new SDK is auto-generated from OpenAPI specs, provides better consistency, and works isomorphically across all JavaScript environments. This package will receive security patches and bug fixes until March 31st but no new features. Migrate to @nhost/nhost-js for future compatibility. Learn more: https://nhost.io/blog/introducing-new-javascript-sdk",
"dependencies": {
"@nhost/graphql-js": "0.3.0",
"@nhost/hasura-auth-js": "2.12.0",
"@nhost/hasura-storage-js": "2.9.0",
"isomorphic-unfetch": "^3.1.0"
},
"peerDependencies": {
"graphql": "^14.0.0 || ^15.0.0 || ^16.0.0"
}
},
"node_modules/@nhost/react": {
"version": "3.11.2",
"resolved": "https://registry.npmjs.org/@nhost/react/-/react-3.11.2.tgz",
"integrity": "sha512-OQLDFltmFo7l11VHoR6b17fNNG5qxGF3UU21md0dglo3exZ2XT3lFqI/6rITIAy5kiXK66ciBAN060Zmy0E4dA==",
"deprecated": "⚠ DEPRECATED: This package is deprecated in favor of @nhost/nhost-js@^4.0.0. The new SDK is auto-generated from OpenAPI specs, provides better consistency, and works isomorphically across all JavaScript environments. This package will receive security patches and bug fixes until March 31st but no new features. Migrate to @nhost/nhost-js for future compatibility. Learn more: https://nhost.io/blog/introducing-new-javascript-sdk",
"dependencies": {
"@nhost/nhost-js": "3.3.1",
"@xstate/react": "^3.2.2",
"jwt-decode": "^4.0.0",
"react-dom": "^18.2.0",
"xstate": "^4.38.3"
},
"peerDependencies": {
"react": "^17.0.0 || ^18.1.0 || ^19.0.0",
"react-dom": "^17.0.0 || ^18.1.0 || ^19.0.0"
}
},
"node_modules/@nhost/react/node_modules/@xstate/react": {
"version": "3.2.2",
"resolved": "https://registry.npmjs.org/@xstate/react/-/react-3.2.2.tgz",
"integrity": "sha512-feghXWLedyq8JeL13yda3XnHPZKwYDN5HPBLykpLeuNpr9178tQd2/3d0NrH6gSd0sG5mLuLeuD+ck830fgzLQ==",
"dependencies": {
"use-isomorphic-layout-effect": "^1.1.2",
"use-sync-external-store": "^1.0.0"
},
"peerDependencies": {
"@xstate/fsm": "^2.0.0",
"react": "^16.8.0 || ^17.0.0 || ^18.0.0",
"xstate": "^4.37.2"
},
"peerDependenciesMeta": {
"@xstate/fsm": {
"optional": true
},
"xstate": {
"optional": true
}
}
},
"node_modules/@nhost/react/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==",
"dependencies": {
"loose-envify": "^1.1.0",
"scheduler": "^0.23.2"
},
"peerDependencies": {
"react": "^18.3.1"
}
},
"node_modules/@nhost/react/node_modules/scheduler": {
"version": "0.23.2",
"resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.23.2.tgz",
"integrity": "sha512-UOShsPwz7NrMUqhR6t0hWjFduvOzbtv7toDH1/hIrfRNIDBnnBWd0CwJTGvTpngVlmwGCdP9/Zl/tVrDqcuYzQ==",
"dependencies": {
"loose-envify": "^1.1.0"
}
},
"node_modules/@nodelib/fs.scandir": { "node_modules/@nodelib/fs.scandir": {
"version": "2.1.5", "version": "2.1.5",
"resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz",
@@ -1432,6 +1605,20 @@
"win32" "win32"
] ]
}, },
"node_modules/@simplewebauthn/browser": {
"version": "9.0.1",
"resolved": "https://registry.npmjs.org/@simplewebauthn/browser/-/browser-9.0.1.tgz",
"integrity": "sha512-wD2WpbkaEP4170s13/HUxPcAV5y4ZXaKo1TfNklS5zDefPinIgXOpgz1kpEvobAsaLPa2KeH7AKKX/od1mrBJw==",
"dependencies": {
"@simplewebauthn/types": "^9.0.1"
}
},
"node_modules/@simplewebauthn/types": {
"version": "9.0.1",
"resolved": "https://registry.npmjs.org/@simplewebauthn/types/-/types-9.0.1.tgz",
"integrity": "sha512-tGSRP1QvsAvsJmnOlRQyw/mvK9gnPtjEc5fg2+m8n+QUa+D7rvrKkOYyfpy42GTs90X3RDOnqJgfHt+qO67/+w==",
"deprecated": "Package no longer supported. Contact Support at https://www.npmjs.com/support for more info."
},
"node_modules/@standard-schema/spec": { "node_modules/@standard-schema/spec": {
"version": "1.0.0", "version": "1.0.0",
"resolved": "https://registry.npmjs.org/@standard-schema/spec/-/spec-1.0.0.tgz", "resolved": "https://registry.npmjs.org/@standard-schema/spec/-/spec-1.0.0.tgz",
@@ -1884,6 +2071,50 @@
"vite": "^4.2.0 || ^5.0.0 || ^6.0.0 || ^7.0.0" "vite": "^4.2.0 || ^5.0.0 || ^6.0.0 || ^7.0.0"
} }
}, },
"node_modules/@wry/caches": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/@wry/caches/-/caches-1.0.1.tgz",
"integrity": "sha512-bXuaUNLVVkD20wcGBWRyo7j9N3TxePEWFZj2Y+r9OoUzfqmavM84+mFykRicNsBqatba5JLay1t48wxaXaWnlA==",
"dependencies": {
"tslib": "^2.3.0"
},
"engines": {
"node": ">=8"
}
},
"node_modules/@wry/context": {
"version": "0.7.4",
"resolved": "https://registry.npmjs.org/@wry/context/-/context-0.7.4.tgz",
"integrity": "sha512-jmT7Sb4ZQWI5iyu3lobQxICu2nC/vbUhP0vIdd6tHC9PTfenmRmuIFqktc6GH9cgi+ZHnsLWPvfSvc4DrYmKiQ==",
"dependencies": {
"tslib": "^2.3.0"
},
"engines": {
"node": ">=8"
}
},
"node_modules/@wry/equality": {
"version": "0.5.7",
"resolved": "https://registry.npmjs.org/@wry/equality/-/equality-0.5.7.tgz",
"integrity": "sha512-BRFORjsTuQv5gxcXsuDXx6oGRhuVsEGwZy6LOzRRfgu+eSfxbhUQ9L9YtSEIuIjY/o7g3iWFjrc5eSY1GXP2Dw==",
"dependencies": {
"tslib": "^2.3.0"
},
"engines": {
"node": ">=8"
}
},
"node_modules/@wry/trie": {
"version": "0.5.0",
"resolved": "https://registry.npmjs.org/@wry/trie/-/trie-0.5.0.tgz",
"integrity": "sha512-FNoYzHawTMk/6KMQoEG5O4PuioX19UbwdQKF44yw0nLfOypfQdjtfZzo/UIJWAJ23sNIFbD1Ug9lbaDGMwbqQA==",
"dependencies": {
"tslib": "^2.3.0"
},
"engines": {
"node": ">=8"
}
},
"node_modules/acorn": { "node_modules/acorn": {
"version": "8.15.0", "version": "8.15.0",
"resolved": "https://registry.npmjs.org/acorn/-/acorn-8.15.0.tgz", "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.15.0.tgz",
@@ -2033,6 +2264,11 @@
"dev": true, "dev": true,
"license": "MIT" "license": "MIT"
}, },
"node_modules/base-64": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/base-64/-/base-64-1.0.0.tgz",
"integrity": "sha512-kwDPIFCGx0NZHog36dj+tHiwP4QMzsZ3AgMViUBKI0+V5n4U0ufTCUMhnQ04diaRI8EX/QcPfql7zlhZ7j4zgg=="
},
"node_modules/baseline-browser-mapping": { "node_modules/baseline-browser-mapping": {
"version": "2.8.6", "version": "2.8.6",
"resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.8.6.tgz", "resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.8.6.tgz",
@@ -2814,6 +3050,14 @@
"reusify": "^1.0.4" "reusify": "^1.0.4"
} }
}, },
"node_modules/fetch-ponyfill": {
"version": "7.1.0",
"resolved": "https://registry.npmjs.org/fetch-ponyfill/-/fetch-ponyfill-7.1.0.tgz",
"integrity": "sha512-FhbbL55dj/qdVO3YNK7ZEkshvj3eQ7EuIGV2I6ic/2YiocvyWv+7jg2s4AyS0wdRU75s3tA8ZxI/xPigb0v5Aw==",
"dependencies": {
"node-fetch": "~2.6.1"
}
},
"node_modules/file-entry-cache": { "node_modules/file-entry-cache": {
"version": "8.0.0", "version": "8.0.0",
"resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-8.0.0.tgz", "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-8.0.0.tgz",
@@ -3024,6 +3268,57 @@
"dev": true, "dev": true,
"license": "MIT" "license": "MIT"
}, },
"node_modules/graphql": {
"version": "16.11.0",
"resolved": "https://registry.npmjs.org/graphql/-/graphql-16.11.0.tgz",
"integrity": "sha512-mS1lbMsxgQj6hge1XZ6p7GPhbrtFwUFYi3wRzXAC/FmYnyXMTvvI3td3rjmQ2u8ewXueaSvRPWaEcgVVOT9Jnw==",
"engines": {
"node": "^12.22.0 || ^14.16.0 || ^16.0.0 || >=17.0.0"
}
},
"node_modules/graphql-tag": {
"version": "2.12.6",
"resolved": "https://registry.npmjs.org/graphql-tag/-/graphql-tag-2.12.6.tgz",
"integrity": "sha512-FdSNcu2QQcWnM2VNvSCCDCVS5PpPqpzgFT8+GXzqJuoDd0CBncxCY278u4mhRO7tMgo2JjgJA5aZ+nWSQ/Z+xg==",
"dependencies": {
"tslib": "^2.1.0"
},
"engines": {
"node": ">=10"
},
"peerDependencies": {
"graphql": "^0.9.0 || ^0.10.0 || ^0.11.0 || ^0.12.0 || ^0.13.0 || ^14.0.0 || ^15.0.0 || ^16.0.0"
}
},
"node_modules/graphql-ws": {
"version": "6.0.6",
"resolved": "https://registry.npmjs.org/graphql-ws/-/graphql-ws-6.0.6.tgz",
"integrity": "sha512-zgfER9s+ftkGKUZgc0xbx8T7/HMO4AV5/YuYiFc+AtgcO5T0v8AxYYNQ+ltzuzDZgNkYJaFspm5MMYLjQzrkmw==",
"engines": {
"node": ">=20"
},
"peerDependencies": {
"@fastify/websocket": "^10 || ^11",
"crossws": "~0.3",
"graphql": "^15.10.1 || ^16",
"uWebSockets.js": "^20",
"ws": "^8"
},
"peerDependenciesMeta": {
"@fastify/websocket": {
"optional": true
},
"crossws": {
"optional": true
},
"uWebSockets.js": {
"optional": true
},
"ws": {
"optional": true
}
}
},
"node_modules/has-flag": { "node_modules/has-flag": {
"version": "4.0.0", "version": "4.0.0",
"resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz",
@@ -3182,6 +3477,15 @@
"dev": true, "dev": true,
"license": "ISC" "license": "ISC"
}, },
"node_modules/isomorphic-unfetch": {
"version": "3.1.0",
"resolved": "https://registry.npmjs.org/isomorphic-unfetch/-/isomorphic-unfetch-3.1.0.tgz",
"integrity": "sha512-geDJjpoZ8N0kWexiwkX8F9NkTsXhetLPVbZFQ+JTW239QNOwvB0gniuR1Wc6f0AMTn7/mFGyXvHTifrCp/GH8Q==",
"dependencies": {
"node-fetch": "^2.6.1",
"unfetch": "^4.2.0"
}
},
"node_modules/jackspeak": { "node_modules/jackspeak": {
"version": "3.4.3", "version": "3.4.3",
"resolved": "https://registry.npmjs.org/jackspeak/-/jackspeak-3.4.3.tgz", "resolved": "https://registry.npmjs.org/jackspeak/-/jackspeak-3.4.3.tgz",
@@ -3208,11 +3512,18 @@
"jiti": "bin/jiti.js" "jiti": "bin/jiti.js"
} }
}, },
"node_modules/js-cookie": {
"version": "3.0.5",
"resolved": "https://registry.npmjs.org/js-cookie/-/js-cookie-3.0.5.tgz",
"integrity": "sha512-cEiJEAEoIbWfCZYKWhVwFuvPX1gETRYPw6LlaTKoxD3s2AkXzkCjnp6h0V77ozyqj0jakteJ4YqDJT830+lVGw==",
"engines": {
"node": ">=14"
}
},
"node_modules/js-tokens": { "node_modules/js-tokens": {
"version": "4.0.0", "version": "4.0.0",
"resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz",
"integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==", "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==",
"dev": true,
"license": "MIT" "license": "MIT"
}, },
"node_modules/js-yaml": { "node_modules/js-yaml": {
@@ -3275,6 +3586,14 @@
"node": ">=6" "node": ">=6"
} }
}, },
"node_modules/jwt-decode": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/jwt-decode/-/jwt-decode-4.0.0.tgz",
"integrity": "sha512-+KJGIyHgkGuIq3IEBNftfhW/LfWhXUIY6OmyVWjliu5KH1y0fw7VQ8YndE2O4qZdMSd9SqbnC8GOcZEy0Om7sA==",
"engines": {
"node": ">=18"
}
},
"node_modules/keyv": { "node_modules/keyv": {
"version": "4.5.4", "version": "4.5.4",
"resolved": "https://registry.npmjs.org/keyv/-/keyv-4.5.4.tgz", "resolved": "https://registry.npmjs.org/keyv/-/keyv-4.5.4.tgz",
@@ -3339,6 +3658,17 @@
"dev": true, "dev": true,
"license": "MIT" "license": "MIT"
}, },
"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==",
"dependencies": {
"js-tokens": "^3.0.0 || ^4.0.0"
},
"bin": {
"loose-envify": "cli.js"
}
},
"node_modules/lru-cache": { "node_modules/lru-cache": {
"version": "5.1.1", "version": "5.1.1",
"resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz", "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz",
@@ -3353,7 +3683,6 @@
"version": "0.544.0", "version": "0.544.0",
"resolved": "https://registry.npmjs.org/lucide-react/-/lucide-react-0.544.0.tgz", "resolved": "https://registry.npmjs.org/lucide-react/-/lucide-react-0.544.0.tgz",
"integrity": "sha512-t5tS44bqd825zAW45UQxpG2CvcC4urOwn2TrwSH8u+MjeE+1NnWl6QqeQ/6NdjMqdOygyiT9p3Ev0p1NJykxjw==", "integrity": "sha512-t5tS44bqd825zAW45UQxpG2CvcC4urOwn2TrwSH8u+MjeE+1NnWl6QqeQ/6NdjMqdOygyiT9p3Ev0p1NJykxjw==",
"license": "ISC",
"peerDependencies": { "peerDependencies": {
"react": "^16.5.1 || ^17.0.0 || ^18.0.0 || ^19.0.0" "react": "^16.5.1 || ^17.0.0 || ^18.0.0 || ^19.0.0"
} }
@@ -3450,6 +3779,25 @@
"dev": true, "dev": true,
"license": "MIT" "license": "MIT"
}, },
"node_modules/node-fetch": {
"version": "2.6.13",
"resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.13.tgz",
"integrity": "sha512-StxNAxh15zr77QvvkmveSQ8uCQ4+v5FkvNTj0OESmiHu+VRi/gXArXtkWMElOsOUNLtUEvI4yS+rdtOHZTwlQA==",
"dependencies": {
"whatwg-url": "^5.0.0"
},
"engines": {
"node": "4.x || >=6.0.0"
},
"peerDependencies": {
"encoding": "^0.1.0"
},
"peerDependenciesMeta": {
"encoding": {
"optional": true
}
}
},
"node_modules/node-releases": { "node_modules/node-releases": {
"version": "2.0.21", "version": "2.0.21",
"resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.21.tgz", "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.21.tgz",
@@ -3497,6 +3845,17 @@
"node": ">= 6" "node": ">= 6"
} }
}, },
"node_modules/optimism": {
"version": "0.18.1",
"resolved": "https://registry.npmjs.org/optimism/-/optimism-0.18.1.tgz",
"integrity": "sha512-mLXNwWPa9dgFyDqkNi54sjDyNJ9/fTI6WGBLgnXku1vdKY/jovHfZT5r+aiVeFFLOz+foPNOm5YJ4mqgld2GBQ==",
"dependencies": {
"@wry/caches": "^1.0.0",
"@wry/context": "^0.7.0",
"@wry/trie": "^0.5.0",
"tslib": "^2.3.0"
}
},
"node_modules/optionator": { "node_modules/optionator": {
"version": "0.9.4", "version": "0.9.4",
"resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.4.tgz", "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.4.tgz",
@@ -4146,6 +4505,15 @@
"queue-microtask": "^1.2.2" "queue-microtask": "^1.2.2"
} }
}, },
"node_modules/rxjs": {
"version": "7.8.2",
"resolved": "https://registry.npmjs.org/rxjs/-/rxjs-7.8.2.tgz",
"integrity": "sha512-dhKf903U/PQZY6boNNtAGdWbG85WAbjT/1xYoZIC7FAY0yWapOBQVsVrDl58W86//e1VpMNBtRV4MaXfdMySFA==",
"peer": true,
"dependencies": {
"tslib": "^2.1.0"
}
},
"node_modules/scheduler": { "node_modules/scheduler": {
"version": "0.26.0", "version": "0.26.0",
"resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.26.0.tgz", "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.26.0.tgz",
@@ -4508,6 +4876,11 @@
"node": ">=8.0" "node": ">=8.0"
} }
}, },
"node_modules/tr46": {
"version": "0.0.3",
"resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz",
"integrity": "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw=="
},
"node_modules/ts-api-utils": { "node_modules/ts-api-utils": {
"version": "2.1.0", "version": "2.1.0",
"resolved": "https://registry.npmjs.org/ts-api-utils/-/ts-api-utils-2.1.0.tgz", "resolved": "https://registry.npmjs.org/ts-api-utils/-/ts-api-utils-2.1.0.tgz",
@@ -4528,6 +4901,11 @@
"dev": true, "dev": true,
"license": "Apache-2.0" "license": "Apache-2.0"
}, },
"node_modules/tslib": {
"version": "2.8.1",
"resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz",
"integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w=="
},
"node_modules/type-check": { "node_modules/type-check": {
"version": "0.4.0", "version": "0.4.0",
"resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz", "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz",
@@ -4579,6 +4957,11 @@
"typescript": ">=4.8.4 <6.0.0" "typescript": ">=4.8.4 <6.0.0"
} }
}, },
"node_modules/unfetch": {
"version": "4.2.0",
"resolved": "https://registry.npmjs.org/unfetch/-/unfetch-4.2.0.tgz",
"integrity": "sha512-F9p7yYCn6cIW9El1zi0HI6vqpeIvBsr3dSuRO6Xuppb1u5rXpCPmMvLSyECLhybr9isec8Ohl0hPekMVrEinDA=="
},
"node_modules/update-browserslist-db": { "node_modules/update-browserslist-db": {
"version": "1.1.3", "version": "1.1.3",
"resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.1.3.tgz", "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.1.3.tgz",
@@ -4620,6 +5003,19 @@
"punycode": "^2.1.0" "punycode": "^2.1.0"
} }
}, },
"node_modules/use-isomorphic-layout-effect": {
"version": "1.2.1",
"resolved": "https://registry.npmjs.org/use-isomorphic-layout-effect/-/use-isomorphic-layout-effect-1.2.1.tgz",
"integrity": "sha512-tpZZ+EX0gaghDAiFR37hj5MgY6ZN55kLiPkJsKxBMZ6GZdOSPJXiOzPM984oPYZ5AnehYx5WQp1+ME8I/P/pRA==",
"peerDependencies": {
"react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0"
},
"peerDependenciesMeta": {
"@types/react": {
"optional": true
}
}
},
"node_modules/use-sync-external-store": { "node_modules/use-sync-external-store": {
"version": "1.5.0", "version": "1.5.0",
"resolved": "https://registry.npmjs.org/use-sync-external-store/-/use-sync-external-store-1.5.0.tgz", "resolved": "https://registry.npmjs.org/use-sync-external-store/-/use-sync-external-store-1.5.0.tgz",
@@ -4764,6 +5160,20 @@
"url": "https://github.com/sponsors/jonschlinkert" "url": "https://github.com/sponsors/jonschlinkert"
} }
}, },
"node_modules/webidl-conversions": {
"version": "3.0.1",
"resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz",
"integrity": "sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ=="
},
"node_modules/whatwg-url": {
"version": "5.0.0",
"resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-5.0.0.tgz",
"integrity": "sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==",
"dependencies": {
"tr46": "~0.0.3",
"webidl-conversions": "^3.0.0"
}
},
"node_modules/which": { "node_modules/which": {
"version": "2.0.2", "version": "2.0.2",
"resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz",
@@ -4885,6 +5295,15 @@
"url": "https://github.com/chalk/ansi-styles?sponsor=1" "url": "https://github.com/chalk/ansi-styles?sponsor=1"
} }
}, },
"node_modules/xstate": {
"version": "4.38.3",
"resolved": "https://registry.npmjs.org/xstate/-/xstate-4.38.3.tgz",
"integrity": "sha512-SH7nAaaPQx57dx6qvfcIgqKRXIh4L0A1iYEqim4s1u7c9VoCgzZc+63FY90AKU4ZzOC2cfJzTnpO4zK7fCUzzw==",
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/xstate"
}
},
"node_modules/yallist": { "node_modules/yallist": {
"version": "3.1.1", "version": "3.1.1",
"resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz", "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz",

عرض الملف

@@ -10,6 +10,10 @@
"preview": "vite preview" "preview": "vite preview"
}, },
"dependencies": { "dependencies": {
"@apollo/client": "^4.0.7",
"@nhost/react": "^3.11.2",
"graphql": "^16.11.0",
"graphql-ws": "^6.0.6",
"lucide-react": "^0.544.0", "lucide-react": "^0.544.0",
"react": "^19.1.1", "react": "^19.1.1",
"react-dom": "^19.1.1", "react-dom": "^19.1.1",

عرض الملف

@@ -23,6 +23,14 @@ function App() {
<Route path="/portfolio" element={<ProtectedRoute><Portfolio /></ProtectedRoute>} /> <Route path="/portfolio" element={<ProtectedRoute><Portfolio /></ProtectedRoute>} />
<Route path="/strategy" element={<ProtectedRoute><Strategy /></ProtectedRoute>} /> <Route path="/strategy" element={<ProtectedRoute><Strategy /></ProtectedRoute>} />
<Route path="/settings" element={<ProtectedRoute><Settings /></ProtectedRoute>} /> <Route path="/settings" element={<ProtectedRoute><Settings /></ProtectedRoute>} />
{/* <Route path="/" element={<Dashboard />} />
<Route path="/portfolio" element={<Portfolio />} />
<Route path="/strategy" element={<Strategy />} />
<Route path="/settings" element={<Settings />} /> */}
</Routes> </Routes>
<Footer /> <Footer />
</div> </div>

عرض الملف

@@ -1,11 +1,106 @@
// بسم الله الرحمن الرحيم
import { StrictMode } from 'react' import { StrictMode } from 'react'
import { createRoot } from 'react-dom/client' import { createRoot } from 'react-dom/client'
import './index.css' import './index.css'
import './App.css' import './App.css'
import App from './App.tsx' import App from './App.tsx'
import { NhostProvider, NhostClient } from '@nhost/react'
import { ApolloClient, InMemoryCache, createHttpLink, split } from "@apollo/client"
import { ApolloProvider } from "@apollo/client/react";
import { setContext } from '@apollo/client/link/context'
import { GraphQLWsLink } from '@apollo/client/link/subscriptions'
import { createClient } from 'graphql-ws'
import { getMainDefinition } from '@apollo/client/utilities'
// Initialize Nhost client
const nhost = new NhostClient({
authUrl: 'https://onekeyword-auth-76aaf2ee939c.hosted.ghaymah.systems',
storageUrl: 'https://onekeyword-auth-76aaf2ee939c.hosted.ghaymah.systems',
functionsUrl: 'https://onekeyword-auth-76aaf2ee939c.hosted.ghaymah.systems',
graphqlUrl: 'https://onekeyword-auth-76aaf2ee939c.hosted.ghaymah.systems',
})
// Auth link for Apollo Client
const authLink = setContext(async (_, { headers }) => {
// Get the authentication state from Nhost
const isAuthenticated = await nhost.auth.isAuthenticatedAsync()
if (isAuthenticated) {
const token = nhost.auth.getAccessToken()
return {
headers: {
...headers,
authorization: token ? `Bearer ${token}` : '',
}
}
} else {
// Use public role when not authenticated
return {
headers: {
...headers,
'x-hasura-role': 'public',
}
}
}
})
// HTTP link for queries and mutations
const httpLink = createHttpLink({
uri: 'https://hasura-bc7db43160df.hosted.ghaymah.systems/v1/graphql',
})
// WebSocket link for subscriptions
const wsLink = new GraphQLWsLink(
createClient({
url: 'wss://hasura-bc7db43160df.hosted.ghaymah.systems/v1/graphql',
connectionParams: async () => {
const isAuthenticated = await nhost.auth.isAuthenticatedAsync()
if (isAuthenticated) {
const token = nhost.auth.getAccessToken()
return {
headers: {
authorization: token ? `Bearer ${token}` : '',
}
}
} else {
return {
headers: {
'x-hasura-role': 'public',
}
}
}
},
})
)
// Split links based on operation type
const splitLink = split(
({ query }) => {
const definition = getMainDefinition(query)
return (
definition.kind === 'OperationDefinition' &&
definition.operation === 'subscription'
)
},
wsLink,
authLink.concat(httpLink)
)
// Apollo Client setup
const client = new ApolloClient({
link: splitLink,
cache: new InMemoryCache(),
})
createRoot(document.getElementById('root')!).render( createRoot(document.getElementById('root')!).render(
<NhostProvider nhost={nhost}>
<StrictMode> <StrictMode>
<ApolloProvider client={client}>
<App /> <App />
</StrictMode>, </ApolloProvider>
</StrictMode>
</NhostProvider>,
) )

عرض الملف

@@ -1,668 +1,377 @@
import { useState } from 'react'; import React, { useState } from 'react';
// Removed: import { useRouter } from 'next/navigation'; // Next.js specific import { Eye, EyeOff, Mail, Lock, User, ArrowLeft, AlertCircle } from 'lucide-react';
// Removed: import { motion } from 'framer-motion'; // External library not available import { useAuth } from './useAuth'; // Adjust the import path as needed
export default function Login() { const Login = () => {
const [email, setEmail] = useState(''); const [isLogin, setIsLogin] = useState(true);
const [password, setPassword] = useState(''); const [showPassword, setShowPassword] = useState(false);
// Replaced useRouter with a simple object for basic navigation const [showForgotPassword, setShowForgotPassword] = useState(false);
const router = { const [forgotPasswordEmail, setForgotPasswordEmail] = useState('');
push: (path: string) => {
window.location.href = path; // Simulating navigation for a generic React environment const [formData, setFormData] = useState({
}, email: '',
}; password: '',
const [isLoading, setIsLoading] = useState(false); confirmPassword: '',
const [error, setError] = useState(''); username: ''
// Removed unused state: const [isHovering, setIsHovering] = useState(false); });
const { isLoading, error, handleLogin, handleSignUp, handleForgotPassword, setError } = useAuth();
const handleSubmit = async (e: React.FormEvent) => { const handleSubmit = async (e: React.FormEvent) => {
e.preventDefault(); e.preventDefault();
setIsLoading(true); setError(''); // Clear previous errors
setError('');
// Validation
if (!isLogin && formData.password !== formData.confirmPassword) {
setError('Passwords do not match');
return;
}
if (!isLogin && formData.password.length < 6) {
setError('Password must be at least 6 characters long');
return;
}
try { try {
const response = await fetch('https://solidpoint-auth.ghaymah.systems/signin/email-password', { let result;
method: 'POST', if (isLogin) {
headers: { result = await handleLogin(formData.email, formData.password);
'Content-Type': 'application/json',
},
body: JSON.stringify({ email, password }),
});
const data = await response.json();
if (response.ok) {
// استخراج البيانات المهمة من الاستجابة
const accessToken = data.session?.accessToken;
const refreshToken = data.session?.refreshToken;
const userId = data.session?.user?.id;
// تخزين بيانات المستخدم في localStorage
const userData = {
email,
accessToken,
userId,
isLoggedIn: true,
lastLogin: new Date().toISOString()
};
localStorage.setItem('sp_user', JSON.stringify(userData));
localStorage.setItem('user_id', userId); // تخزين user_id بشكل منفصل للمكونات التي تحتاجه
window.dispatchEvent(new Event('userChanged'));
// تخزين الـ tokens في HttpOnly cookies
try {
const tokenResponse = await fetch('/api/auth/store-tokens', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
credentials: 'include',
body: JSON.stringify({
access_token: accessToken,
refresh_token: refreshToken,
expires_in: 3600 // مدة صلاحية الـ token بالثواني (ساعة واحدة)
})
});
if (!tokenResponse.ok) {
console.error('Failed to store token in HttpOnly cookie');
} else { } else {
console.log('Token stored successfully in HttpOnly cookie'); result = await handleSignUp(formData.email, formData.password);
}
} catch (error) {
console.error('Error storing token in HttpOnly cookie:', error);
} }
// Show success animation before navigating if (result.success) {
const successElement = document.createElement('div'); // Redirect or handle successful authentication
successElement.className = 'fixed inset-0 flex items-center justify-center bg-white/80 dark:bg-gray-900/80 backdrop-blur-sm z-50'; console.log('Authentication successful:', result.data);
successElement.innerHTML = ` // You can redirect here or handle the successful login
<div class="text-center p-8 bg-white dark:bg-gray-800 rounded-lg shadow-lg"> window.location.href = '/'; // Example redirect
<svg class="w-16 h-16 mx-auto text-green-500" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M5 13l4 4L19 7"></path>
</svg>
<h2 class="mt-3 text-xl font-bold text-gray-900 dark:text-white">Welcome back!</h2>
<p class="mt-2 text-gray-600 dark:text-gray-300">You have successfully logged in.</p>
</div>
`;
document.body.appendChild(successElement);
// Add animation with CSS
const checkmark = successElement.querySelector('svg');
if (checkmark) {
checkmark.style.transform = 'scale(0)';
checkmark.style.transition = 'transform 0.5s ease-out';
setTimeout(() => {
checkmark.style.transform = 'scale(1)';
}, 100);
}
// Navigate after animation completes
setTimeout(() => {
// Remove the success element before navigating
document.body.removeChild(successElement);
router.push('/');
}, 1800);
} else { } else {
setError(data?.message || 'Failed to login. Please check your credentials.'); // Error is already set in the hook, no need to set it again
console.error('Authentication failed:', result.error);
} }
} catch (err: any) { } catch (err) {
setError('Failed to login. Please try again.'); console.error('Authentication error:', err);
} finally {
setIsLoading(false);
} }
}; };
const handleSubmitSignUp = async (e: React.FormEvent) => { const handleForgotPasswordSubmit = async (e: React.FormEvent) => {
e.preventDefault(); e.preventDefault();
setIsLoading(true);
setError(''); setError('');
try { if (!forgotPasswordEmail) {
const response = await fetch('https://solidpoint-auth.ghaymah.systems/signup/email-password', { setError('Please enter your email address');
method: 'POST', return;
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({ email, password }), // Add more fields like name if needed
});
const data = await response.json();
console.log(data);
if (response.ok) {
const accessToken = data.session?.accessToken;
const refreshToken = data.session?.refreshToken;
const userId = data.session?.user?.id;
const userData = {
email,
accessToken,
userId,
isLoggedIn: true,
lastLogin: new Date().toISOString()
};
localStorage.setItem('sp_user', JSON.stringify(userData));
localStorage.setItem('user_id', userId);
window.dispatchEvent(new Event('userChanged'));
// Store tokens in HttpOnly cookies
try {
const tokenResponse = await fetch('/api/auth/store-tokens', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
credentials: 'include',
body: JSON.stringify({
access_token: accessToken,
refresh_token: refreshToken,
expires_in: 3600
})
});
if (!tokenResponse.ok) {
console.error('Failed to store token in HttpOnly cookie');
} else {
console.log('Token stored successfully in HttpOnly cookie');
}
} catch (error) {
console.error('Error storing token in HttpOnly cookie:', error);
} }
// Success animation const result = await handleForgotPassword(forgotPasswordEmail);
const successElement = document.createElement('div'); if (result.success) {
successElement.className = 'fixed inset-0 flex items-center justify-center bg-white/80 dark:bg-gray-900/80 backdrop-blur-sm z-50'; setError(''); // Clear any previous errors
successElement.innerHTML = ` alert(result.message); // Or show a success message in a better way
<div class="text-center p-8 bg-white dark:bg-gray-800 rounded-lg shadow-lg"> setShowForgotPassword(false);
<svg class="w-16 h-16 mx-auto text-green-500" fill="none" stroke="currentColor" viewBox="0 0 24 24"> setForgotPasswordEmail('');
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M5 13l4 4L19 7"></path>
</svg>
<h2 class="mt-3 text-xl font-bold text-gray-900 dark:text-white">Account Created!</h2>
<p class="mt-2 text-gray-600 dark:text-gray-300">You have successfully signed up.</p>
</div>
`;
document.body.appendChild(successElement);
const checkmark = successElement.querySelector('svg');
if (checkmark) {
checkmark.style.transform = 'scale(0)';
checkmark.style.transition = 'transform 0.5s ease-out';
setTimeout(() => {
checkmark.style.transform = 'scale(1)';
}, 100);
}
setTimeout(() => {
document.body.removeChild(successElement);
router.push('/');
}, 1800);
} else {
setError(data?.message || 'Signup failed. Please check your info.');
}
} catch (err: any) {
setError('Signup failed. Please try again.');
} finally {
setIsLoading(false);
} }
}; };
const handleChange = (e: React.ChangeEvent<HTMLInputElement>) => {
const [swapAuth, setSwapAuth] = useState(false); setFormData(prev => ({
...prev,
const swapAuthFunction = () => { [e.target.name]: e.target.value
setSwapAuth(!swapAuth); }));
setEmail('');
setPassword('');
setError('');
}
const handleSubmitForgotPassword = async (e: React.FormEvent) => {
e.preventDefault();
setIsLoading(true);
setError('');
// setMessage('');
try {
const res = await fetch('https://solidpoint-auth.ghaymah.systems/user/password/reset', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ email }),
});
if (res.ok) {
// setMessage('If the email exists, a reset link has been sent.');
console.log('If the email exists, a reset link has been sent.');
} else {
const data = await res.json();
setError(data.message || 'Failed to send reset email.');
}
} catch {
setError('Failed to send reset email. Try again later.');
} finally {
setIsLoading(false);
}
}; };
const [forgotPassword, setForgotPassword] = useState(false); // Forgot Password Modal
if (showForgotPassword) {
if (forgotPassword) {
return ( return (
<div className="min-h-screen w-full bg-gradient-to-br from-blue-50 via-indigo-50 to-purple-50 dark:from-gray-800 dark:via-indigo-900 dark:to-purple-900 font-inter relative overflow-hidden"> <div className="min-h-screen bg-gradient-to-br from-blue-50 to-indigo-100 flex items-center justify-center p-4">
<div className="absolute top-0 left-0 w-full h-full overflow-hidden z-0 opacity-20 dark:opacity-10"> <div className="max-w-md w-full">
<div className="absolute top-10 left-10 w-72 h-72 bg-blue-400 dark:bg-blue-600 rounded-full mix-blend-multiply filter blur-2xl"></div> <div className="bg-white rounded-2xl shadow-lg border p-6 sm:p-8">
<div className="absolute top-0 right-10 w-72 h-72 bg-purple-400 dark:bg-purple-600 rounded-full mix-blend-multiply filter blur-2xl"></div> {/* Back Button */}
<div className="absolute -bottom-8 left-20 w-72 h-72 bg-indigo-400 dark:bg-indigo-600 rounded-full mix-blend-multiply filter blur-2xl"></div> <button
</div> onClick={() => setShowForgotPassword(false)}
className="flex items-center gap-2 text-gray-600 hover:text-gray-800 mb-4 transition-colors"
<div className="grid grid-cols-1 lg:grid-cols-[40%_60%] min-h-screen gap-0 md:gap-4 relative z-10">
<div className="flex flex-col justify-center px-8 sm:px-12 py-12 bg-white/80 dark:bg-gray-800/90 backdrop-blur-md shadow-lg rounded-xl border border-white/20 dark:border-gray-700/30 transition-all duration-200">
{/* Logo */}
<div className="flex items-center justify-center mb-6">
<div className="relative flex items-center justify-center h-12 w-12">
<div className="absolute w-11 h-11 rounded-full border-[1.5px] border-blue-500/40"></div>
<div className="absolute w-10 h-10 rounded-full border-2 border-blue-600/70"></div>
<div className="absolute w-10 h-10 rounded-full bg-gradient-to-r from-blue-500/20 to-indigo-600/20 blur-md"></div>
<div className="absolute w-9 h-9 rounded-full bg-gradient-to-tr from-blue-500/30 to-indigo-600/5 blur-[6px]"></div>
<div className="absolute w-8 h-8 rounded-full border-[1px] border-blue-600/60 shadow-[0_0_12px_rgba(59,130,246,0.4)] shadow-[0_0_18px_rgba(79,70,229,0.5)]"></div>
</div>
<span className="ml-3 text-2xl font-bold bg-gradient-to-r from-blue-500 to-indigo-600 bg-clip-text text-transparent">SolidPoint</span>
</div>
<h1 className="text-3xl font-bold mb-4 bg-gradient-to-r from-blue-600 to-indigo-700 bg-clip-text text-transparent text-center">
Welcome Back!
</h1>
<p className="text-sm mb-6 text-gray-600 dark:text-gray-300 text-center">
Please enter your credentials to access your account.
</p>
{error && (
<div
className="p-3 mb-4 text-red-700 bg-red-100 dark:bg-red-900/30 dark:text-red-300 rounded-lg border border-red-200 dark:border-red-800 flex items-center space-x-2 text-sm"
> >
<svg xmlns="http://www.w3.org/2000/svg" className="h-5 w-5 flex-shrink-0" fill="none" viewBox="0 0 24 24" stroke="currentColor"> <ArrowLeft className="w-4 h-4" />
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M12 8v4m0 4h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z" /> Back to {isLogin ? 'Login' : 'Sign Up'}
</svg> </button>
{/* Header */}
<div className="text-center mb-6">
<h1 className="text-2xl font-extrabold text-[#4285F4] mb-2">
Reset Your Password
</h1>
<p className="text-gray-600 text-sm">
Enter your email address and we'll send you a link to reset your password.
</p>
</div>
{/* Error Display */}
{error && (
<div className="mb-4 p-3 bg-red-50 border border-red-200 rounded-lg flex items-center gap-2 text-red-700 text-sm">
<AlertCircle className="w-4 h-4 flex-shrink-0" />
<span>{error}</span> <span>{error}</span>
</div> </div>
)} )}
<form onSubmit={handleSubmitForgotPassword} className="space-y-4 mt-4"> {/* Forgot Password Form */}
{/* <form onSubmit={swapAuth ? handleSubmitSignUp : forgotPassword ? handleSubmitForgotPassword : handleSubmit} className="space-y-4 mt-4"> */} <form onSubmit={handleForgotPasswordSubmit} className="space-y-4">
<div className="relative"> <div>
<label htmlFor="email" className="block text-sm font-medium mb-1">Email Address</label> <label className="block text-sm font-medium text-gray-700 mb-2">
Email Address
</label>
<div className="relative"> <div className="relative">
<div className="absolute inset-y-0 left-0 pl-3 flex items-center pointer-events-none"> <div className="absolute inset-y-0 left-0 pl-3 flex items-center pointer-events-none">
<svg xmlns="http://www.w3.org/2000/svg" className="h-5 w-5 text-gray-400" fill="none" viewBox="0 0 24 24" stroke="currentColor"> <Mail className="h-5 w-5 text-gray-400" />
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M3 8l7.89 5.26a2 2 0 002.22 0L21 8M5 19h14a2 2 0 002-2V7a2 2 0 00-2-2H5a2 2 0 00-2 2v10a2 2 0 002 2z" />
</svg>
</div> </div>
<input <input
id="email"
name="email"
type="email" type="email"
value={forgotPasswordEmail}
onChange={(e) => setForgotPasswordEmail(e.target.value)}
className="block w-full pl-10 pr-3 py-3 border border-gray-300 rounded-lg focus:ring-2 focus:ring-[#4285F4] focus:border-[#4285F4] transition-colors"
placeholder="Enter your email"
required required
className="w-full pl-10 pr-3 py-2.5 rounded-lg border border-gray-300 dark:border-gray-600 bg-white/50 dark:bg-gray-800/50 backdrop-blur-sm focus:ring-2 focus:ring-blue-500 focus:border-transparent text-sm transition-all duration-300 hover:border-blue-300 dark:hover:border-blue-500 "
placeholder="your.email@example.com"
value={email}
onChange={(e) => setEmail(e.target.value)}
/> />
</div> </div>
</div> </div>
<button <button
type="submit" type="submit"
disabled={isLoading} disabled={isLoading}
className="w-full py-3 px-4 bg-gradient-to-r from-blue-600 to-indigo-700 hover:from-blue-700 hover:to-indigo-800 text-white font-medium rounded-lg shadow-md transition-all duration-200 disabled:opacity-70 text-sm flex items-center justify-center space-x-2" className="w-full bg-[#4285F4] text-white py-3 px-4 rounded-lg font-semibold hover:bg-[#1e40af] focus:ring-2 focus:ring-[#4285F4] focus:ring-offset-2 transition-colors disabled:opacity-50 disabled:cursor-not-allowed"
> >
{isLoading ? ( {isLoading ? 'Sending...' : 'Send Reset Link'}
<>
<svg className="animate-spin -ml-1 mr-2 h-4 w-4 text-white" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24">
<circle className="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" strokeWidth="4"></circle>
<path className="opacity-75" fill="currentColor" d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z"></path>
</svg>
<span>
{
swapAuth ?
"Signing up..."
:
"Logging in..."
}
</span>
</>
) : (
<>
<span>Get Password</span>
<svg xmlns="http://www.w3.org/2000/svg" className="h-4 w-4" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M14 5l7 7m0 0l-7 7m7-7H3" />
</svg>
</>
)}
</button>
<div className="relative my-6">
<div className="absolute inset-0 flex items-center">
<div className="w-full border-t border-gray-300 dark:border-gray-700"></div>
</div>
<div className="relative flex justify-center text-sm">
<span className="px-2 bg-white/80 dark:bg-gray-800/80 backdrop-blur-sm text-gray-500 dark:text-gray-400 rounded-md">
remember your password?
</span>
</div>
</div>
<div
className="w-full flex items-center justify-center py-2.5 px-4 border border-gray-300 dark:border-gray-600 rounded-lg shadow-sm bg-white/70 dark:bg-gray-800/70 backdrop-blur-sm hover:bg-gray-50 dark:hover:bg-gray-700 transition-all duration-200"
onClick={() => setForgotPassword(false)}
>
<svg className="w-5 h-5 mr-2" fill="none" stroke="currentColor" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M11 16l-4-4m0 0l4-4m-4 4h14m-5 4v1a3 3 0 01-3 3H6a3 3 0 01-3-3V7a3 3 0 013-3h7a3 3 0 013 3v1" />
</svg>
<span className="text-sm font-medium text-gray-700 dark:text-gray-300">
Back to Login
</span>
</div>
</form>
</div>
<RightSide />
</div>
</div>
)
}
return (
// Replaced motion.div with standard div and adopted SignUp's overall layout
<div className="min-h-screen w-full bg-gradient-to-br from-blue-50 via-indigo-50 to-purple-50 dark:from-gray-800 dark:via-indigo-900 dark:to-purple-900 font-inter relative overflow-hidden">
{/* Background decorative elements - optimized for performance */}
<div className="absolute top-0 left-0 w-full h-full overflow-hidden z-0 opacity-20 dark:opacity-10">
<div className="absolute top-10 left-10 w-72 h-72 bg-blue-400 dark:bg-blue-600 rounded-full mix-blend-multiply filter blur-2xl"></div>
<div className="absolute top-0 right-10 w-72 h-72 bg-purple-400 dark:bg-purple-600 rounded-full mix-blend-multiply filter blur-2xl"></div>
<div className="absolute -bottom-8 left-20 w-72 h-72 bg-indigo-400 dark:bg-indigo-600 rounded-full mix-blend-multiply filter blur-2xl"></div>
</div>
{/* Page split with 35%/65% ratio and responsiveness */}
<div className="grid grid-cols-1 lg:grid-cols-[40%_60%] min-h-screen gap-0 md:gap-4 relative z-10">
{/* Left side - Login Form (aligned with SignUp's form side) */}
<div className="flex flex-col justify-center px-8 sm:px-12 py-12 bg-white/80 dark:bg-gray-800/90 backdrop-blur-md shadow-lg rounded-xl border border-white/20 dark:border-gray-700/30 transition-all duration-200">
{/* Logo */}
<div className="flex items-center justify-center mb-6">
<div className="relative flex items-center justify-center h-12 w-12">
<div className="absolute w-11 h-11 rounded-full border-[1.5px] border-blue-500/40"></div>
<div className="absolute w-10 h-10 rounded-full border-2 border-blue-600/70"></div>
<div className="absolute w-10 h-10 rounded-full bg-gradient-to-r from-blue-500/20 to-indigo-600/20 blur-md"></div>
<div className="absolute w-9 h-9 rounded-full bg-gradient-to-tr from-blue-500/30 to-indigo-600/5 blur-[6px]"></div>
<div className="absolute w-8 h-8 rounded-full border-[1px] border-blue-600/60 shadow-[0_0_12px_rgba(59,130,246,0.4)] shadow-[0_0_18px_rgba(79,70,229,0.5)]"></div>
</div>
<span className="ml-3 text-2xl font-bold bg-gradient-to-r from-blue-500 to-indigo-600 bg-clip-text text-transparent">SolidPoint</span>
</div>
<h1 className="text-3xl font-bold mb-4 bg-gradient-to-r from-blue-600 to-indigo-700 bg-clip-text text-transparent text-center">
Welcome Back!
</h1>
<p className="text-sm mb-6 text-gray-600 dark:text-gray-300 text-center">
Please enter your credentials to access your account.
</p>
{error && (
<div
className="p-3 mb-4 text-red-700 bg-red-100 dark:bg-red-900/30 dark:text-red-300 rounded-lg border border-red-200 dark:border-red-800 flex items-center space-x-2 text-sm"
>
<svg xmlns="http://www.w3.org/2000/svg" className="h-5 w-5 flex-shrink-0" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M12 8v4m0 4h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z" />
</svg>
<span>{error}</span>
</div>
)}
<form onSubmit={swapAuth ? handleSubmitSignUp : handleSubmit} className="space-y-4 mt-4">
<div className="relative">
<label htmlFor="email" className="block text-sm font-medium mb-1">Email Address</label>
<div className="relative">
<div className="absolute inset-y-0 left-0 pl-3 flex items-center pointer-events-none">
<svg xmlns="http://www.w3.org/2000/svg" className="h-5 w-5 text-gray-400" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M3 8l7.89 5.26a2 2 0 002.22 0L21 8M5 19h14a2 2 0 002-2V7a2 2 0 00-2-2H5a2 2 0 00-2 2v10a2 2 0 002 2z" />
</svg>
</div>
<input
id="email"
name="email"
type="email"
required
className="w-full pl-10 pr-3 py-2.5 rounded-lg border border-gray-300 dark:border-gray-600 bg-white/50 dark:bg-gray-800/50 backdrop-blur-sm focus:ring-2 focus:ring-blue-500 focus:border-transparent text-sm transition-all duration-300 hover:border-blue-300 dark:hover:border-blue-500 dark:text-white"
placeholder="your.email@example.com"
value={email}
onChange={(e) => setEmail(e.target.value)}
/>
</div>
</div>
<div className="relative">
<label htmlFor="password" className="block text-sm font-medium mb-1">Password</label>
<div className="relative">
<div className="absolute inset-y-0 left-0 pl-3 flex items-center pointer-events-none">
<svg xmlns="http://www.w3.org/2000/svg" className="h-5 w-5 text-gray-400" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M12 15v2m-6 4h12a2 2 0 002-2v-6a2 2 0 00-2-2H6a2 2 0 00-2 2v6a2 2 0 002 2zm10-10V7a4 4 0 00-8 0v4h8z" />
</svg>
</div>
<input
id="password"
name="password"
type="password"
required
className="w-full pl-10 pr-3 py-2.5 rounded-lg border border-gray-300 dark:border-gray-600 bg-white/50 dark:bg-gray-800/50 backdrop-blur-sm focus:ring-2 focus:ring-blue-500 focus:border-transparent text-sm transition-all duration-300 hover:border-blue-300 dark:hover:border-blue-500 dark:text-white"
placeholder="••••••••"
value={password}
onChange={(e) => setPassword(e.target.value)}
/>
<button
type="button"
className="absolute inset-y-0 right-0 pr-3 flex items-center text-gray-400"
onClick={() => {
const input = document.getElementById('password') as HTMLInputElement;
input.type = input.type === 'password' ? 'text' : 'password';
}}
>
<svg xmlns="http://www.w3.org/2000/svg" className="h-5 w-5" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M15 12a3 3 0 11-6 0 3 3 0 016 0z" />
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M2.458 12C3.732 7.943 7.523 5 12 5c4.478 0 8.268 2.943 9.542 7-1.274 4.057-5.064 7-9.542 7-4.477 0-8.268-2.943-9.542-7z" />
</svg>
</button>
</div>
</div>
{
!forgotPassword && (
<div style={{ margin: '10px auto 5px', color: 'blue', cursor: 'pointer', width: '100%', textAlign: 'center' }} onClick={() => setForgotPassword(!forgotPassword)}>
Forgot password?
</div>
)
}
<button
type="submit"
disabled={isLoading}
className="w-full py-3 px-4 bg-gradient-to-r from-blue-600 to-indigo-700 hover:from-blue-700 hover:to-indigo-800 text-white font-medium rounded-lg shadow-md transition-all duration-200 disabled:opacity-70 text-sm flex items-center justify-center space-x-2"
>
{isLoading ? (
<>
<svg className="animate-spin -ml-1 mr-2 h-4 w-4 text-white" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24">
<circle className="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" strokeWidth="4"></circle>
<path className="opacity-75" fill="currentColor" d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z"></path>
</svg>
<span>
{
swapAuth ?
"Signing up..."
:
"Logging in..."
}
</span>
</>
) : (
<>
<span>{swapAuth ? "Sign Up" : "Login"}</span>
<svg xmlns="http://www.w3.org/2000/svg" className="h-4 w-4" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M14 5l7 7m0 0l-7 7m7-7H3" />
</svg>
</>
)}
</button>
<div className="relative my-6">
<div className="absolute inset-0 flex items-center">
<div className="w-full border-t border-gray-300 dark:border-gray-700"></div>
</div>
<div className="relative flex justify-center text-sm">
<span className="px-2 bg-white/80 dark:bg-gray-800/80 backdrop-blur-sm text-gray-500 dark:text-gray-400 rounded-md">
{
swapAuth ? "Already have an account" : "Don't have an account"
}
</span>
</div>
</div>
<button
type="button"
className="w-full flex items-center justify-center py-2.5 px-4 border border-gray-300 dark:border-gray-600 rounded-lg shadow-sm bg-white/70 dark:bg-gray-800/70 backdrop-blur-sm hover:bg-gray-50 dark:hover:bg-gray-700 transition-all duration-200"
onClick={swapAuthFunction}
>
<svg className="w-5 h-5 mr-2" fill="none" stroke="currentColor" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M11 16l-4-4m0 0l4-4m-4 4h14m-5 4v1a3 3 0 01-3 3H6a3 3 0 01-3-3V7a3 3 0 013-3h7a3 3 0 013 3v1" />
</svg>
<span className="text-sm font-medium text-gray-700 dark:text-gray-300">
{
swapAuth ?
"Login"
:
"Create an account"
}
</span>
</button> </button>
</form> </form>
</div> </div>
<RightSide />
</div> </div>
</div> </div>
); );
} }
const RightSide = () => {
return ( return (
<div className="flex flex-col justify-center px-6 sm:px-12 py-8 text-gray-900 dark:text-white bg-white/10 dark:bg-gray-800/20 backdrop-blur-md rounded-xl border border-white/20 dark:border-gray-700/30 shadow-lg transition-all duration-200"> <div className="min-h-screen bg-gradient-to-br from-blue-50 to-indigo-100 flex items-center justify-center p-4">
<h2 className="text-2xl font-bold mb-6 bg-gradient-to-r from-blue-500 to-indigo-600 bg-clip-text text-transparent text-center">Why Choose SolidPoint?</h2> <div className="max-w-md w-full">
{/* Header */}
{/* Platform Purpose */} <div className="text-center mb-8">
<div className="mb-4 bg-white/20 dark:bg-white/5 p-4 rounded-xl border border-indigo-500/40 dark:border-indigo-500/20 shadow-md hover:shadow-md transition-all duration-200 hover:border-indigo-500/60 dark:hover:border-indigo-500/40 hover:bg-indigo-500/10"> <h1 className="text-3xl font-extrabold text-[#4285F4] mb-2">
<h3 className="text-lg font-bold mb-2 bg-gradient-to-r from-blue-400 to-indigo-500 bg-clip-text text-transparent flex items-center"> {isLogin ? 'Welcome Back' : 'Create Account'}
<div className="shrink-0 bg-gradient-to-br from-blue-500 to-indigo-600 p-1.5 rounded-lg shadow-md mr-2"> </h1>
<svg xmlns="http://www.w3.org/2000/svg" className="h-5 w-5 text-white" fill="none" viewBox="0 0 24 24" stroke="currentColor"> <p className="text-gray-600">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M9.663 17h4.673M12 3v1m6.364 1.636l-.707.707M21 12h-1M4 12H3m3.343-5.657l-.707-.707m2.828 9.9a5 5 0 117.072 0l-.548.547A3.374 3.374 0 0014 18.469V19a2 2 0 11-4 0v-.531c0-.895-.356-1.754-.988-2.386l-.548-.547z" /> {isLogin ? 'Sign in to your account' : 'Create your account'}
</svg> </p>
</div>
Platform Purpose
</h3>
<ul className="space-y-1 text-gray-800 dark:text-gray-300 ml-4 font-medium">
<li className="flex items-center">
<svg className="w-4 h-4 mr-2 text-indigo-600 dark:text-indigo-400" fill="currentColor" viewBox="0 0 20 20" xmlns="http://www.w3.org/2000/svg"><path fillRule="evenodd" d="M10 18a8 8 0 100-16 8 8 0 000 16zm3.707-9.293a1 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" clipRule="evenodd"></path></svg>
SolidPoint is an AI-powered content summarization platform
</li>
<li className="flex items-center">
<svg className="w-4 h-4 mr-2 text-indigo-600 dark:text-indigo-400" fill="currentColor" viewBox="0 0 20 20" xmlns="http://www.w3.org/2000/svg"><path fillRule="evenodd" d="M10 18a8 8 0 100-16 8 8 0 000 16zm3.707-9.293a1 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" clipRule="evenodd"></path></svg>
Helps users save time by extracting key insights from various content types
</li>
</ul>
</div> </div>
{/* Key Features */} {/* Auth Card */}
<div className="mb-4 bg-white/20 dark:bg-white/5 p-4 rounded-xl border border-purple-500/40 dark:border-purple-500/20 shadow-md hover:shadow-md transition-all duration-200 hover:border-purple-500/60 dark:hover:border-purple-500/40 hover:bg-purple-500/10"> <div className="bg-white rounded-2xl shadow-lg border p-6 sm:p-8">
<h3 className="text-lg font-bold mb-2 bg-gradient-to-r from-purple-400 to-indigo-500 bg-clip-text text-transparent flex items-center"> {/* Error Display */}
<div className="shrink-0 bg-gradient-to-br from-purple-500 to-indigo-600 p-1.5 rounded-lg shadow-md mr-2"> {error && (
<svg xmlns="http://www.w3.org/2000/svg" className="h-5 w-5 text-white" fill="none" viewBox="0 0 24 24" stroke="currentColor"> <div className="mb-6 p-3 bg-red-50 border border-red-200 rounded-lg flex items-center gap-2 text-red-700 text-sm">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M11.049 2.927c.3-.921 1.603-.921 1.902 0l1.519 4.674a1 1 0 00.95.69h4.915c.969 0 1.371 1.24.588 1.81l-3.976 2.888a1 1 0 00-.363 1.118l1.518 4.674c.3.922-.755 1.688-1.538 1.118l-3.976-2.888a1 1 0 00-1.176 0l-3.976 2.888c-.783.57-1.838-.197-1.538-1.118l1.518-4.674a1 1 0 00-.363-1.118l-3.976-2.888c-.784-.57-.38-1.81.588-1.81h4.914a1 1 0 00.951-.69l1.519-4.674z" /> <AlertCircle className="w-4 h-4 flex-shrink-0" />
</svg> <span>{error}</span>
</div> </div>
Key Features Highlighted )}
</h3>
<ul className="space-y-1 text-gray-800 dark:text-gray-300 ml-4"> <form onSubmit={handleSubmit} className="space-y-6">
<li className="flex items-center"> {/* Username Field - Only for Signup */}
<svg className="w-4 h-4 mr-2 text-blue-600 dark:text-blue-400" fill="currentColor" viewBox="0 0 20 20" xmlns="http://www.w3.org/2000/svg"><path fillRule="evenodd" d="M10 18a8 8 0 100-16 8 8 0 000 16zm3.707-9.293a1 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" clipRule="evenodd"></path></svg> {!isLogin && (
<span className="font-medium text-blue-700 dark:text-blue-300">Security:</span> <div>
<span className="ml-1 text-gray-800 dark:text-gray-300">Data protection and privacy</span> <label className="block text-sm font-medium text-gray-700 mb-2">
</li> Username
<li className="flex items-center"> </label>
<svg className="w-4 h-4 mr-2 text-purple-600 dark:text-purple-400" fill="currentColor" viewBox="0 0 20 20" xmlns="http://www.w3.org/2000/svg"><path fillRule="evenodd" d="M10 18a8 8 0 100-16 8 8 0 000 16zm3.707-9.293a1 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" clipRule="evenodd"></path></svg> <div className="relative">
<span className="font-medium text-purple-700 dark:text-purple-300">Summarization:</span> <div className="absolute inset-y-0 left-0 pl-3 flex items-center pointer-events-none">
<span className="ml-1 text-gray-800 dark:text-gray-300">AI-powered content condensation</span> <User className="h-5 w-5 text-gray-400" />
</li> </div>
<li className="flex items-center"> <input
<svg className="w-4 h-4 mr-2 text-pink-600 dark:text-pink-400" fill="currentColor" viewBox="0 0 20 20" xmlns="http://www.w3.org/2000/svg"><path fillRule="evenodd" d="M10 18a8 8 0 100-16 8 8 0 000 16zm3.707-9.293a1 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" clipRule="evenodd"></path></svg> type="text"
<span className="font-medium text-pink-700 dark:text-pink-300">Performance:</span> name="username"
<span className="ml-1 text-gray-800 dark:text-gray-300">Fast and reliable service</span> value={formData.username}
</li> onChange={handleChange}
<li className="flex items-center"> className="block w-full pl-10 pr-3 py-3 border border-gray-300 rounded-lg focus:ring-2 focus:ring-[#4285F4] focus:border-[#4285F4] transition-colors"
<svg className="w-4 h-4 mr-2 text-green-600 dark:text-green-400" fill="currentColor" viewBox="0 0 20 20" xmlns="http://www.w3.org/2000/svg"><path fillRule="evenodd" d="M10 18a8 8 0 100-16 8 8 0 000 16zm3.707-9.293a1 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" clipRule="evenodd"></path></svg> placeholder="Enter your username"
<span className="font-medium text-green-700 dark:text-green-300">Pricing:</span> required={!isLogin}
<span className="ml-1 text-gray-800 dark:text-gray-300">Cost-effective plans for different needs</span> />
</li> </div>
</ul> </div>
)}
{/* Email Field */}
<div>
<label className="block text-sm font-medium text-gray-700 mb-2">
Email Address
</label>
<div className="relative">
<div className="absolute inset-y-0 left-0 pl-3 flex items-center pointer-events-none">
<Mail className="h-5 w-5 text-gray-400" />
</div>
<input
type="email"
name="email"
value={formData.email}
onChange={handleChange}
className="block w-full pl-10 pr-3 py-3 border border-gray-300 rounded-lg focus:ring-2 focus:ring-[#4285F4] focus:border-[#4285F4] transition-colors"
placeholder="Enter your email"
required
/>
</div>
</div> </div>
{/* Target Use Cases */} {/* Password Field */}
<div className="bg-white/10 p-3 rounded-xl border border-teal-500/30 shadow-md hover:shadow-md transition-all duration-200 hover:border-teal-500/40 hover:bg-teal-500/10"> <div>
<h3 className="text-lg font-bold mb-1 bg-gradient-to-r from-teal-400 to-blue-500 bg-clip-text text-transparent flex items-center"> <label className="block text-sm font-medium text-gray-700 mb-2">
<div className="shrink-0 bg-gradient-to-br from-teal-500 to-blue-600 p-1 rounded-lg shadow-md mr-2"> Password
<svg xmlns="http://www.w3.org/2000/svg" className="h-4 w-4 text-white" fill="none" viewBox="0 0 24 24" stroke="currentColor"> </label>
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2" /> <div className="relative">
<div className="absolute inset-y-0 left-0 pl-3 flex items-center pointer-events-none">
<Lock className="h-5 w-5 text-gray-400" />
</div>
<input
type={showPassword ? 'text' : 'password'}
name="password"
value={formData.password}
onChange={handleChange}
className="block w-full pl-10 pr-10 py-3 border border-gray-300 rounded-lg focus:ring-2 focus:ring-[#4285F4] focus:border-[#4285F4] transition-colors"
placeholder="Enter your password"
required
/>
<button
type="button"
className="absolute inset-y-0 right-0 pr-3 flex items-center"
onClick={() => setShowPassword(!showPassword)}
>
{showPassword ? (
<EyeOff className="h-5 w-5 text-gray-400" />
) : (
<Eye className="h-5 w-5 text-gray-400" />
)}
</button>
</div>
</div>
{/* Confirm Password Field - Only for Signup */}
{!isLogin && (
<div>
<label className="block text-sm font-medium text-gray-700 mb-2">
Confirm Password
</label>
<div className="relative">
<div className="absolute inset-y-0 left-0 pl-3 flex items-center pointer-events-none">
<Lock className="h-5 w-5 text-gray-400" />
</div>
<input
type={showPassword ? 'text' : 'password'}
name="confirmPassword"
value={formData.confirmPassword}
onChange={handleChange}
className="block w-full pl-10 pr-3 py-3 border border-gray-300 rounded-lg focus:ring-2 focus:ring-[#4285F4] focus:border-[#4285F4] transition-colors"
placeholder="Confirm your password"
required={!isLogin}
/>
</div>
</div>
)}
{/* Forgot Password - Only for Login */}
{isLogin && (
<div className="flex justify-end">
<button
type="button"
onClick={() => setShowForgotPassword(true)}
className="text-sm text-[#4285F4] hover:text-[#1e40af] transition-colors"
>
Forgot your password?
</button>
</div>
)}
{/* Submit Button */}
<button
type="submit"
disabled={isLoading}
className="w-full bg-[#4285F4] text-white py-3 px-4 rounded-lg font-semibold hover:bg-[#1e40af] focus:ring-2 focus:ring-[#4285F4] focus:ring-offset-2 transition-colors disabled:opacity-50 disabled:cursor-not-allowed"
>
{isLoading ? (
<span className="flex items-center justify-center gap-2">
<div className="w-4 h-4 border-2 border-white border-t-transparent rounded-full animate-spin" />
{isLogin ? 'Signing In...' : 'Creating Account...'}
</span>
) : (
isLogin ? 'Sign In' : 'Create Account'
)}
</button>
{/* Divider */}
<div className="relative">
<div className="absolute inset-0 flex items-center">
<div className="w-full border-t border-gray-300" />
</div>
<div className="relative flex justify-center text-sm">
<span className="px-2 bg-white text-gray-500">Or continue with</span>
</div>
</div>
{/* Social Login Buttons */}
<div className="grid grid-cols-2 gap-3">
<button
type="button"
disabled={isLoading}
className="w-full inline-flex justify-center py-3 px-4 border border-gray-300 rounded-lg bg-white text-sm font-medium text-gray-700 hover:bg-gray-50 transition-colors disabled:opacity-50 disabled:cursor-not-allowed"
>
<svg className="w-5 h-5" viewBox="0 0 24 24">
<path fill="currentColor" d="M22.56 12.25c0-.78-.07-1.53-.2-2.25H12v4.26h5.92c-.26 1.37-1.04 2.53-2.21 3.31v2.77h3.57c2.08-1.92 3.28-4.74 3.28-8.09z"/>
<path fill="currentColor" d="M12 23c2.97 0 5.46-.98 7.28-2.66l-3.57-2.77c-.98.66-2.23 1.06-3.71 1.06-2.86 0-5.29-1.93-6.16-4.53H2.18v2.84C3.99 20.53 7.7 23 12 23z"/>
<path fill="currentColor" d="M5.84 14.09c-.22-.66-.35-1.36-.35-2.09s.13-1.43.35-2.09V7.07H2.18C1.43 8.55 1 10.22 1 12s.43 3.45 1.18 4.93l2.85-2.22.81-.62z"/>
<path fill="currentColor" d="M12 5.38c1.62 0 3.06.56 4.21 1.64l3.15-3.15C17.45 2.09 14.97 1 12 1 7.7 1 3.99 3.47 2.18 7.07l3.66 2.84c.87-2.6 3.3-4.53 6.16-4.53z"/>
</svg> </svg>
</div> <span className="ml-2">Google</span>
Target Use Cases </button>
</h3> <button
<div className="grid grid-cols-1 md:grid-cols-2 gap-1 ml-1 transition-all duration-200"> type="button"
<div className="flex items-center p-1.5 rounded-lg hover:bg-white/20 dark:hover:bg-gray-800/30 transition-all duration-200 hover:shadow-sm"> disabled={isLoading}
<div className="shrink-0 bg-red-500/30 p-1.5 rounded-full mr-2"> className="w-full inline-flex justify-center py-3 px-4 border border-gray-300 rounded-lg bg-white text-sm font-medium text-gray-700 hover:bg-gray-50 transition-colors disabled:opacity-50 disabled:cursor-not-allowed"
<svg xmlns="http://www.w3.org/2000/svg" className="h-4 w-4 text-red-600 dark:text-red-500" viewBox="0 0 24 24" fill="currentColor"> >
<path d="M19.615 3.184c-3.604-.246-11.631-.245-15.23 0-3.897.266-4.356 2.62-4.385 8.816.029 6.185.484 8.549 4.385 8.816 3.6.245 11.626.246 15.23 0 3.897-.266 4.356-2.62 4.385-8.816-.029-6.185-.484-8.549-4.385-8.816zm-10.615 12.816v-8l8 3.993-8 4.007z" /> <svg className="w-5 h-5" fill="currentColor" viewBox="0 0 24 24">
<path d="M24 4.557c-.883.392-1.832.656-2.828.775 1.017-.609 1.798-1.574 2.165-2.724-.951.564-2.005.974-3.127 1.195-.897-.957-2.178-1.555-3.594-1.555-3.179 0-5.515 2.966-4.797 6.045-4.091-.205-7.719-2.165-10.148-5.144-1.29 2.213-.669 5.108 1.523 6.574-.806-.026-1.566-.247-2.229-.616-.054 2.281 1.581 4.415 3.949 4.89-.693.188-1.452.232-2.224.084.626 1.956 2.444 3.379 4.6 3.419-2.07 1.623-4.678 2.348-7.29 2.04 2.179 1.397 4.768 2.212 7.548 2.212 9.142 0 14.307-7.721 13.995-14.646.962-.695 1.797-1.562 2.457-2.549z"/>
</svg> </svg>
<span className="ml-2">Twitter</span>
</button>
</div> </div>
<span className="text-gray-900 dark:text-gray-300 text-sm font-medium">YouTube video summarization</span> </form>
{/* Toggle between Login and Signup */}
<div className="mt-6 text-center">
<p className="text-sm text-gray-600">
{isLogin ? "Don't have an account? " : "Already have an account? "}
<button
type="button"
onClick={() => {
setIsLogin(!isLogin);
setError(''); // Clear errors when switching modes
}}
disabled={isLoading}
className="text-[#4285F4] hover:text-[#1e40af] font-semibold transition-colors disabled:opacity-50"
>
{isLogin ? 'Sign up' : 'Sign in'}
</button>
</p>
</div> </div>
<div className="flex items-center p-1.5 rounded-lg hover:bg-white/20 dark:hover:bg-gray-800/30 transition-all duration-200 hover:shadow-sm">
<div className="shrink-0 bg-orange-500/30 p-1.5 rounded-full mr-2">
<img src="/reddit_logo.jpeg" className="h-4 w-4 rounded-full" alt="Reddit logo" />
</div> </div>
<span className="text-gray-900 dark:text-gray-300 text-sm font-medium">Reddit post summarization</span>
</div> {/* Footer */}
<div className="flex items-center p-1.5 rounded-lg hover:bg-white/20 dark:hover:bg-gray-800/30 transition-all duration-200 hover:shadow-sm"> <div className="text-center mt-6">
<div className="shrink-0 bg-blue-500/30 p-1.5 rounded-full mr-2"> <p className="text-xs text-gray-500">
<svg xmlns="http://www.w3.org/2000/svg" className="h-4 w-4 text-blue-600 dark:text-blue-400" fill="none" viewBox="0 0 24 24" stroke="currentColor"> By continuing, you agree to our{' '}
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M19.428 15.428a2 2 0 00-1.022-.547l-2.387-.477a6 6 0 00-3.86.517l-.318.158a6 6 0 01-3.86.517L6.05 15.21a2 2 0 00-1.806.547M8 4h8l-1 1v5.172a2 2 0 00.586 1.414l5 5c1.26 1.26.367 3.414-1.415 3.414H4.828c-1.782 0-2.674-2.154-1.414-3.414l5-5A2 2 0 009 10.172V5L8 4z" /> <button className="text-[#4285F4] hover:text-[#1e40af] transition-colors">
</svg> Terms of Service
</div> </button>{' '}
<span className="text-gray-900 dark:text-gray-300 text-sm font-medium">arXiv paper summarization</span> and{' '}
</div> <button className="text-[#4285F4] hover:text-[#1e40af] transition-colors">
<div className="flex items-center p-1.5 rounded-lg hover:bg-white/20 dark:hover:bg-gray-800/30 transition-all duration-200 hover:shadow-sm"> Privacy Policy
<div className="shrink-0 bg-green-500/30 p-1.5 rounded-full mr-2"> </button>
<svg xmlns="http://www.w3.org/2000/svg" className="h-4 w-4 text-green-600 dark:text-green-400" fill="none" viewBox="0 0 24 24" stroke="currentColor"> </p>
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M21 12a9 9 0 01-9 9m9-9a9 9 0 00-9-9m9 9H3m9 9a9 9 0 01-9-9m9 9c1.657 0 3-4.03 3-9s-1.343-9-3-9m0 18c-1.657 0-3-4.03-3-9s1.343-9 3-9m-9 9a9 9 0 019-9" />
</svg>
</div>
<span className="text-gray-900 dark:text-gray-300 text-sm font-medium">Website content summarization</span>
</div>
<div className="flex items-center p-1.5 rounded-lg hover:bg-white/20 dark:hover:bg-gray-800/30 transition-all duration-200 hover:shadow-sm md:col-span-2">
<div className="shrink-0 bg-purple-500/30 p-1.5 rounded-full mr-2">
<svg xmlns="http://www.w3.org/2000/svg" className="h-4 w-4 text-purple-600 dark:text-purple-400" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M7 21h10a2 2 0 002-2V9.414a1 1 0 00-.293-.707l-5.414-5.414A1 1 0 0012.586 3H7a2 2 0 00-2 2v14a2 2 0 002 2z" />
</svg>
</div>
<span className="text-gray-900 dark:text-gray-300 text-sm font-medium">PDF to flashcard conversion</span>
</div> </div>
</div> </div>
</div> </div>
</div> );
) };
}
export default Login;

213
src/pages/useAuth.tsx Normal file
عرض الملف

@@ -0,0 +1,213 @@
import { useState } from 'react';
import { useNhostClient } from '@nhost/react';
export const useAuth = () => {
const [isLoading, setIsLoading] = useState(false);
const [error, setError] = useState('');
const nhost = useNhostClient();
const handleLogin = async (email: string, password: string) => {
setIsLoading(true);
setError('');
try {
const { session, error } = await nhost.auth.signIn({
email,
password,
});
if (error) {
throw new Error(error.message || 'Failed to login. Please check your credentials.');
}
if (session) {
const accessToken = session.accessToken;
const refreshToken = session.refreshToken;
const userId = session.user?.id;
// Store user data in localStorage
const userData = {
email,
accessToken,
userId,
isLoggedIn: true,
lastLogin: new Date().toISOString()
};
localStorage.setItem('sp_user', JSON.stringify(userData));
localStorage.setItem('user_id', userId);
window.dispatchEvent(new Event('userChanged'));
// Store tokens in HttpOnly cookies (if still needed)
try {
await fetch('/api/auth/store-tokens', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
credentials: 'include',
body: JSON.stringify({
access_token: accessToken,
refresh_token: refreshToken,
expires_in: 3600
})
});
} catch (error) {
console.error('Error storing token in HttpOnly cookie:', error);
}
return { success: true, data: { session } };
} else {
throw new Error('Login failed: No session returned');
}
} catch (err) {
const errorMessage = err instanceof Error ? err.message : 'An unknown error occurred';
setError(errorMessage);
return { success: false, error: errorMessage };
} finally {
setIsLoading(false);
}
};
const handleSignUp = async (email: string, password: string) => {
setIsLoading(true);
setError('');
try {
const { session, error } = await nhost.auth.signUp({
email,
password,
});
if (error) {
throw new Error(error.message || 'Signup failed. Please check your info.');
}
if (session) {
const accessToken = session.accessToken;
const refreshToken = session.refreshToken;
const userId = session.user?.id;
const userData = {
email,
accessToken,
userId,
isLoggedIn: true,
lastLogin: new Date().toISOString()
};
localStorage.setItem('sp_user', JSON.stringify(userData));
localStorage.setItem('user_id', userId);
window.dispatchEvent(new Event('userChanged'));
// Store tokens in HttpOnly cookies (if still needed)
try {
await fetch('/api/auth/store-tokens', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
credentials: 'include',
body: JSON.stringify({
access_token: accessToken,
refresh_token: refreshToken,
expires_in: 3600
})
});
} catch (error) {
console.error('Error storing token in HttpOnly cookie:', error);
}
return { success: true, data: { session } };
} else {
// Note: NHost may not return a session immediately if email verification is required
return {
success: true,
data: {
message: 'Signup successful! Please check your email for verification.'
}
};
}
} catch (err) {
const errorMessage = err instanceof Error ? err.message : 'An unknown error occurred';
setError(errorMessage);
return { success: false, error: errorMessage };
} finally {
setIsLoading(false);
}
};
const handleForgotPassword = async (email: string) => {
setIsLoading(true);
setError('');
try {
const { error } = await nhost.auth.resetPassword({ email });
if (error) {
throw new Error(error.message || 'Failed to send reset email.');
}
return {
success: true,
message: 'If the email exists, a reset link has been sent.'
};
} catch (err) {
const errorMessage = err instanceof Error ? err.message : 'An unknown error occurred';
setError(errorMessage);
return { success: false, error: errorMessage };
} finally {
setIsLoading(false);
}
};
// Additional NHost auth methods you might find useful
const handleSignOut = async () => {
setIsLoading(true);
setError('');
try {
const { error } = await nhost.auth.signOut();
if (error) {
throw new Error(error.message || 'Failed to sign out.');
}
// Clear local storage
localStorage.removeItem('sp_user');
localStorage.removeItem('user_id');
window.dispatchEvent(new Event('userChanged'));
return { success: true };
} catch (err) {
const errorMessage = err instanceof Error ? err.message : 'An unknown error occurred';
setError(errorMessage);
return { success: false, error: errorMessage };
} finally {
setIsLoading(false);
}
};
const handleChangePassword = async (newPassword: string) => {
setIsLoading(true);
setError('');
try {
const { error } = await nhost.auth.changePassword({ newPassword });
if (error) {
throw new Error(error.message || 'Failed to change password.');
}
return { success: true, message: 'Password changed successfully.' };
} catch (err) {
const errorMessage = err instanceof Error ? err.message : 'An unknown error occurred';
setError(errorMessage);
return { success: false, error: errorMessage };
} finally {
setIsLoading(false);
}
};
return {
isLoading,
error,
handleLogin,
handleSignUp,
handleForgotPassword,
handleSignOut,
handleChangePassword,
setError
};
};