13 Commits

Author SHA1 Message Date
Suvodip
daa8c8d878 3d model 2025-04-01 14:19:16 +05:30
Suvodip
d01b0aaa2a pull from staging 2025-03-31 19:34:38 +05:30
8f8c5f0d65 Merge pull request 'last work on invoice generation' (#24) from avatar into staging
Reviewed-on: #24
2025-03-31 08:26:26 +00:00
Suvodip
0438c30c97 last work on invoice generation 2025-03-31 13:55:35 +05:30
8b75fa057d Merge pull request 'last work on invoice functionality' (#17) from avatar into staging
Reviewed-on: #17
2025-03-28 14:58:36 +00:00
Suvodip
c927fd6087 last work on invoice functionality 2025-03-28 20:26:50 +05:30
Kar
1ed908b12e Merge pull request '404' (#16) from 404 into staging
Reviewed-on: #16
2025-03-27 15:08:14 +00:00
ffae4acebd 404 2025-03-27 20:36:20 +05:30
daa4702904 Merge pull request 's11' (#15) from login-session into staging
Reviewed-on: #15
2025-03-27 15:03:47 +00:00
a9eabcb683 htaccess 2025-03-27 20:30:50 +05:30
66bd9ec890 public 2025-03-27 19:53:41 +05:30
9d3ef1a643 security.txt 2025-03-27 19:32:07 +05:30
1661f7859d norms 2025-03-27 19:28:11 +05:30
25 changed files with 4794 additions and 102 deletions

251
package-lock.json generated
View File

@@ -10,8 +10,12 @@
"dependencies": {
"@astrojs/react": "^4.2.1",
"@astrojs/tailwind": "^6.0.0",
"@babylonjs/core": "^8.0.2",
"@babylonjs/loaders": "^8.0.2",
"@google/model-viewer": "^4.0.0",
"@radix-ui/react-dialog": "^1.1.6",
"@radix-ui/react-select": "^2.1.6",
"@radix-ui/react-toast": "^1.2.6",
"@shadcn/ui": "^0.0.4",
"@types/react": "^19.0.12",
"astro": "^5.5.2",
@@ -21,6 +25,8 @@
"lucide-react": "^0.484.0",
"pocketbase": "^0.25.2",
"postcss": "^8.5.3",
"react-router-dom": "^7.4.1",
"react-to-print": "^3.0.5",
"tailwind-merge": "^3.0.2",
"tailwindcss": "^3.4.17",
"tailwindcss-animate": "^1.0.7"
@@ -438,6 +444,22 @@
"node": ">=6.9.0"
}
},
"node_modules/@babylonjs/core": {
"version": "8.0.2",
"resolved": "https://registry.npmjs.org/@babylonjs/core/-/core-8.0.2.tgz",
"integrity": "sha512-Bq6Ue2mu6N351AhPqoPNT0jLMm3n9igAqLs9CLb4RiWNR+VBylIf+L+lw/HJ6L2iOOzT1YfY7jD+vg6mW5wY2A==",
"license": "Apache-2.0"
},
"node_modules/@babylonjs/loaders": {
"version": "8.0.2",
"resolved": "https://registry.npmjs.org/@babylonjs/loaders/-/loaders-8.0.2.tgz",
"integrity": "sha512-o9e5GUiysqpqsTfYf2EQhG4OZM+Tx4Cp1uy/6XKuk+xBqKjCp4cId0QDFkB3CISatB7BbC/TrSpgR5TuJ5/+Pw==",
"license": "Apache-2.0",
"peerDependencies": {
"@babylonjs/core": "^8.0.0",
"babylonjs-gltf2interface": "^8.0.0"
}
},
"node_modules/@emnapi/runtime": {
"version": "1.3.1",
"resolved": "https://registry.npmjs.org/@emnapi/runtime/-/runtime-1.3.1.tgz",
@@ -886,6 +908,22 @@
"integrity": "sha512-MDWhGtE+eHw5JW7lq4qhc5yRLS11ERl1c7Z6Xd0a58DozHES6EnNNwUWbMiG4J9Cgj053Bhk8zvlhFYKVhULwg==",
"license": "MIT"
},
"node_modules/@google/model-viewer": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/@google/model-viewer/-/model-viewer-4.0.0.tgz",
"integrity": "sha512-hf4mixP178gGtvWQq97a0uULr7k98BwJGRU3lvt12XnurOdLmZA/RPdW4tPsINNNQxZvmvQsjud4lZWre6WGNw==",
"license": "Apache-2.0",
"dependencies": {
"@monogrid/gainmap-js": "^3.0.1",
"lit": "^2.7.2"
},
"engines": {
"node": ">=6.0.0"
},
"peerDependencies": {
"three": "^0.169.0"
}
},
"node_modules/@img/sharp-darwin-arm64": {
"version": "0.33.5",
"resolved": "https://registry.npmjs.org/@img/sharp-darwin-arm64/-/sharp-darwin-arm64-0.33.5.tgz",
@@ -1352,6 +1390,33 @@
"@jridgewell/sourcemap-codec": "^1.4.14"
}
},
"node_modules/@lit-labs/ssr-dom-shim": {
"version": "1.3.0",
"resolved": "https://registry.npmjs.org/@lit-labs/ssr-dom-shim/-/ssr-dom-shim-1.3.0.tgz",
"integrity": "sha512-nQIWonJ6eFAvUUrSlwyHDm/aE8PBDu5kRpL0vHMg6K8fK3Diq1xdPjTnsJSwxABhaZ+5eBi1btQB5ShUTKo4nQ==",
"license": "BSD-3-Clause"
},
"node_modules/@lit/reactive-element": {
"version": "1.6.3",
"resolved": "https://registry.npmjs.org/@lit/reactive-element/-/reactive-element-1.6.3.tgz",
"integrity": "sha512-QuTgnG52Poic7uM1AN5yJ09QMe0O28e10XzSvWDz02TJiiKee4stsiownEIadWm8nYzyDAyT+gKzUoZmiWQtsQ==",
"license": "BSD-3-Clause",
"dependencies": {
"@lit-labs/ssr-dom-shim": "^1.0.0"
}
},
"node_modules/@monogrid/gainmap-js": {
"version": "3.1.0",
"resolved": "https://registry.npmjs.org/@monogrid/gainmap-js/-/gainmap-js-3.1.0.tgz",
"integrity": "sha512-Obb0/gEd/HReTlg8ttaYk+0m62gQJmCblMOjHSMHRrBP2zdfKMHLCRbh/6ex9fSUJMKdjjIEiohwkbGD3wj2Nw==",
"license": "MIT",
"dependencies": {
"promise-worker-transferable": "^1.0.4"
},
"peerDependencies": {
"three": ">= 0.159.0"
}
},
"node_modules/@nodelib/fs.scandir": {
"version": "2.1.5",
"resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz",
@@ -1794,6 +1859,40 @@
}
}
},
"node_modules/@radix-ui/react-toast": {
"version": "1.2.6",
"resolved": "https://registry.npmjs.org/@radix-ui/react-toast/-/react-toast-1.2.6.tgz",
"integrity": "sha512-gN4dpuIVKEgpLn1z5FhzT9mYRUitbfZq9XqN/7kkBMUgFTzTG8x/KszWJugJXHcwxckY8xcKDZPz7kG3o6DsUA==",
"license": "MIT",
"dependencies": {
"@radix-ui/primitive": "1.1.1",
"@radix-ui/react-collection": "1.1.2",
"@radix-ui/react-compose-refs": "1.1.1",
"@radix-ui/react-context": "1.1.1",
"@radix-ui/react-dismissable-layer": "1.1.5",
"@radix-ui/react-portal": "1.1.4",
"@radix-ui/react-presence": "1.1.2",
"@radix-ui/react-primitive": "2.0.2",
"@radix-ui/react-use-callback-ref": "1.1.0",
"@radix-ui/react-use-controllable-state": "1.1.0",
"@radix-ui/react-use-layout-effect": "1.1.0",
"@radix-ui/react-visually-hidden": "1.1.2"
},
"peerDependencies": {
"@types/react": "*",
"@types/react-dom": "*",
"react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc",
"react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
},
"peerDependenciesMeta": {
"@types/react": {
"optional": true
},
"@types/react-dom": {
"optional": true
}
}
},
"node_modules/@radix-ui/react-use-callback-ref": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/@radix-ui/react-use-callback-ref/-/react-use-callback-ref-1.1.0.tgz",
@@ -2429,6 +2528,12 @@
"@types/react": "^19.0.0"
}
},
"node_modules/@types/trusted-types": {
"version": "2.0.7",
"resolved": "https://registry.npmjs.org/@types/trusted-types/-/trusted-types-2.0.7.tgz",
"integrity": "sha512-ScaPdn1dQczgbl0QFTeTOmVHFULt394XJgOQNoyVhZ6r2vLnMLJfBPd53SB52T/3G36VI1/g2MZaX0cwDuXsfw==",
"license": "MIT"
},
"node_modules/@types/unist": {
"version": "3.0.3",
"resolved": "https://registry.npmjs.org/@types/unist/-/unist-3.0.3.tgz",
@@ -2747,6 +2852,13 @@
"node": ">= 0.4"
}
},
"node_modules/babylonjs-gltf2interface": {
"version": "8.0.2",
"resolved": "https://registry.npmjs.org/babylonjs-gltf2interface/-/babylonjs-gltf2interface-8.0.2.tgz",
"integrity": "sha512-H913H03OSNfRlq0HLwrUjaJMmqYdINZPsX7dfa0Wg4mXmUgEIWSPxPi1Bl4gVwVGemZ8/PsFYbeOtyofwZBFJA==",
"license": "Apache-2.0",
"peer": true
},
"node_modules/bail": {
"version": "2.0.2",
"resolved": "https://registry.npmjs.org/bail/-/bail-2.0.2.tgz",
@@ -4076,6 +4188,12 @@
],
"license": "BSD-3-Clause"
},
"node_modules/immediate": {
"version": "3.0.6",
"resolved": "https://registry.npmjs.org/immediate/-/immediate-3.0.6.tgz",
"integrity": "sha512-XXOFtyqDjNDAQxVfYxuF7g9Il/IbWmmlQg2MYKOH8ExIT1qg6xc4zyS3HaEEATgs1btfzxq15ciUiY7gjSXRGQ==",
"license": "MIT"
},
"node_modules/import-meta-resolve": {
"version": "4.1.0",
"resolved": "https://registry.npmjs.org/import-meta-resolve/-/import-meta-resolve-4.1.0.tgz",
@@ -4231,6 +4349,12 @@
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/is-promise": {
"version": "2.2.2",
"resolved": "https://registry.npmjs.org/is-promise/-/is-promise-2.2.2.tgz",
"integrity": "sha512-+lP4/6lKUBfQjZ2pdxThZvLUAafmZb8OAxFb8XXtiQmS35INgr85hdOGoEs124ez1FCnZJt6jau/T+alh58QFQ==",
"license": "MIT"
},
"node_modules/is-stream": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/is-stream/-/is-stream-3.0.0.tgz",
@@ -4363,6 +4487,15 @@
"node": ">=6"
}
},
"node_modules/lie": {
"version": "3.3.0",
"resolved": "https://registry.npmjs.org/lie/-/lie-3.3.0.tgz",
"integrity": "sha512-UaiMJzeWRlEujzAuw5LokY1L5ecNQYZKfmyZ9L7wDHb/p5etKaxXhohBcrw0EYby+G/NA52vRSN4N39dxHAIwQ==",
"license": "MIT",
"dependencies": {
"immediate": "~3.0.5"
}
},
"node_modules/lilconfig": {
"version": "3.1.3",
"resolved": "https://registry.npmjs.org/lilconfig/-/lilconfig-3.1.3.tgz",
@@ -4381,6 +4514,37 @@
"integrity": "sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==",
"license": "MIT"
},
"node_modules/lit": {
"version": "2.8.0",
"resolved": "https://registry.npmjs.org/lit/-/lit-2.8.0.tgz",
"integrity": "sha512-4Sc3OFX9QHOJaHbmTMk28SYgVxLN3ePDjg7hofEft2zWlehFL3LiAuapWc4U/kYwMYJSh2hTCPZ6/LIC7ii0MA==",
"license": "BSD-3-Clause",
"dependencies": {
"@lit/reactive-element": "^1.6.0",
"lit-element": "^3.3.0",
"lit-html": "^2.8.0"
}
},
"node_modules/lit-element": {
"version": "3.3.3",
"resolved": "https://registry.npmjs.org/lit-element/-/lit-element-3.3.3.tgz",
"integrity": "sha512-XbeRxmTHubXENkV4h8RIPyr8lXc+Ff28rkcQzw3G6up2xg5E8Zu1IgOWIwBLEQsu3cOVFqdYwiVi0hv0SlpqUA==",
"license": "BSD-3-Clause",
"dependencies": {
"@lit-labs/ssr-dom-shim": "^1.1.0",
"@lit/reactive-element": "^1.3.0",
"lit-html": "^2.8.0"
}
},
"node_modules/lit-html": {
"version": "2.8.0",
"resolved": "https://registry.npmjs.org/lit-html/-/lit-html-2.8.0.tgz",
"integrity": "sha512-o9t+MQM3P4y7M7yNzqAyjp7z+mQGa4NS4CxiyLqFPyFWyc4O+nodLrkrxSaCTrla6M5YOLaT3RpbbqjszB5g3Q==",
"license": "BSD-3-Clause",
"dependencies": {
"@types/trusted-types": "^2.0.2"
}
},
"node_modules/log-symbols": {
"version": "5.1.0",
"resolved": "https://registry.npmjs.org/log-symbols/-/log-symbols-5.1.0.tgz",
@@ -5870,6 +6034,16 @@
"node": ">=6"
}
},
"node_modules/promise-worker-transferable": {
"version": "1.0.4",
"resolved": "https://registry.npmjs.org/promise-worker-transferable/-/promise-worker-transferable-1.0.4.tgz",
"integrity": "sha512-bN+0ehEnrXfxV2ZQvU2PetO0n4gqBD4ulq3MI1WOPLgr7/Mg9yRQkX5+0v1vagr74ZTsl7XtzlaYDo2EuCeYJw==",
"license": "Apache-2.0",
"dependencies": {
"is-promise": "^2.1.0",
"lie": "^3.0.2"
}
},
"node_modules/prompts": {
"version": "2.4.2",
"resolved": "https://registry.npmjs.org/prompts/-/prompts-2.4.2.tgz",
@@ -6007,6 +6181,55 @@
}
}
},
"node_modules/react-router": {
"version": "7.4.1",
"resolved": "https://registry.npmjs.org/react-router/-/react-router-7.4.1.tgz",
"integrity": "sha512-Vmizn9ZNzxfh3cumddqv3kLOKvc7AskUT0dC1prTabhiEi0U4A33LmkDOJ79tXaeSqCqMBXBU/ySX88W85+EUg==",
"license": "MIT",
"dependencies": {
"@types/cookie": "^0.6.0",
"cookie": "^1.0.1",
"set-cookie-parser": "^2.6.0",
"turbo-stream": "2.4.0"
},
"engines": {
"node": ">=20.0.0"
},
"peerDependencies": {
"react": ">=18",
"react-dom": ">=18"
},
"peerDependenciesMeta": {
"react-dom": {
"optional": true
}
}
},
"node_modules/react-router-dom": {
"version": "7.4.1",
"resolved": "https://registry.npmjs.org/react-router-dom/-/react-router-dom-7.4.1.tgz",
"integrity": "sha512-L3/4tig0Lvs6m6THK0HRV4eHUdpx0dlJasgCxXKnavwhh4tKYgpuZk75HRYNoRKDyDWi9QgzGXsQ1oQSBlWpAA==",
"license": "MIT",
"dependencies": {
"react-router": "7.4.1"
},
"engines": {
"node": ">=20.0.0"
},
"peerDependencies": {
"react": ">=18",
"react-dom": ">=18"
}
},
"node_modules/react-router/node_modules/cookie": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/cookie/-/cookie-1.0.2.tgz",
"integrity": "sha512-9Kr/j4O16ISv8zBBhJoi4bXOYNTkFLOqSL3UDB0njXxCXNezjeyVrJyGOWtgfs/q2km1gwBcfH8q1yEGoMYunA==",
"license": "MIT",
"engines": {
"node": ">=18"
}
},
"node_modules/react-style-singleton": {
"version": "2.2.3",
"resolved": "https://registry.npmjs.org/react-style-singleton/-/react-style-singleton-2.2.3.tgz",
@@ -6029,6 +6252,15 @@
}
}
},
"node_modules/react-to-print": {
"version": "3.0.5",
"resolved": "https://registry.npmjs.org/react-to-print/-/react-to-print-3.0.5.tgz",
"integrity": "sha512-Z15MwMOzYCHWi26CZeFNwflAg7Nr8uWD6FTj+EkfIOjYyjr0MXGbI0c7rF4Fgrbj3XG9hFndb1ourxpPz2RAiA==",
"license": "MIT",
"peerDependencies": {
"react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ~19"
}
},
"node_modules/read-cache": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/read-cache/-/read-cache-1.0.0.tgz",
@@ -6463,6 +6695,12 @@
"node": ">=10"
}
},
"node_modules/set-cookie-parser": {
"version": "2.7.1",
"resolved": "https://registry.npmjs.org/set-cookie-parser/-/set-cookie-parser-2.7.1.tgz",
"integrity": "sha512-IOc8uWeOZgnb3ptbCURJWNjWUPcO3ZnTTdzsurqERrP6nPyv+paC55vJM0LpOlT2ne+Ix+9+CRG1MNLlyZ4GjQ==",
"license": "MIT"
},
"node_modules/sharp": {
"version": "0.33.5",
"resolved": "https://registry.npmjs.org/sharp/-/sharp-0.33.5.tgz",
@@ -6919,6 +7157,13 @@
"node": ">=0.8"
}
},
"node_modules/three": {
"version": "0.169.0",
"resolved": "https://registry.npmjs.org/three/-/three-0.169.0.tgz",
"integrity": "sha512-Ed906MA3dR4TS5riErd4QBsRGPcx+HBDX2O5yYE5GqJeFQTPU+M56Va/f/Oph9X7uZo3W3o4l2ZhBZ6f6qUv0w==",
"license": "MIT",
"peer": true
},
"node_modules/tinyexec": {
"version": "0.3.2",
"resolved": "https://registry.npmjs.org/tinyexec/-/tinyexec-0.3.2.tgz",
@@ -7005,6 +7250,12 @@
"integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==",
"license": "0BSD"
},
"node_modules/turbo-stream": {
"version": "2.4.0",
"resolved": "https://registry.npmjs.org/turbo-stream/-/turbo-stream-2.4.0.tgz",
"integrity": "sha512-FHncC10WpBd2eOmGwpmQsWLDoK4cqsA/UT/GqNoaKOQnT8uzhtCbg3EoUDMvqpOSAI0S26mr0rkjzbOO6S3v1g==",
"license": "ISC"
},
"node_modules/type-fest": {
"version": "4.37.0",
"resolved": "https://registry.npmjs.org/type-fest/-/type-fest-4.37.0.tgz",

View File

@@ -12,8 +12,12 @@
"dependencies": {
"@astrojs/react": "^4.2.1",
"@astrojs/tailwind": "^6.0.0",
"@babylonjs/core": "^8.0.2",
"@babylonjs/loaders": "^8.0.2",
"@google/model-viewer": "^4.0.0",
"@radix-ui/react-dialog": "^1.1.6",
"@radix-ui/react-select": "^2.1.6",
"@radix-ui/react-toast": "^1.2.6",
"@shadcn/ui": "^0.0.4",
"@types/react": "^19.0.12",
"astro": "^5.5.2",
@@ -23,6 +27,8 @@
"lucide-react": "^0.484.0",
"pocketbase": "^0.25.2",
"postcss": "^8.5.3",
"react-router-dom": "^7.4.1",
"react-to-print": "^3.0.5",
"tailwind-merge": "^3.0.2",
"tailwindcss": "^3.4.17",
"tailwindcss-animate": "^1.0.7"

8
public/.htaccess Normal file
View File

@@ -0,0 +1,8 @@
RewriteEngine On
#RewriteCond %{HTTPS} !=on
#RewriteRule ^(.*)$ https://%{HTTP_HOST}%{REQUEST_URI} [L,R=301,NE]
#RewriteCond %{HTTP_HOST} ^www\.(.+)$ [NC]
#RewriteRule ^(.*)$ https://%1/$1 [R=301,L]
RewriteCond %{REQUEST_FILENAME} !-f
RewriteCond %{REQUEST_FILENAME} !-d
RewriteRule ^(.*) data-not-updated-or-not-found

View File

@@ -0,0 +1,7 @@
Contact: mailto:suvankar@siliconpin.com
Contact: https://siliconpin.com/contact/
Expires: 2027-03-26T18:30:00.000Z
Encryption: https://siliconpin.com/pgp-key.txt
Acknowledgments: https://dwd.siliconpin.com
Preferred-Languages: en,bn
CSAF: https://siliconpin.com/.well-known/csaf/provider-metadata.json

41
public/public-key.asc Normal file
View File

@@ -0,0 +1,41 @@
-----BEGIN PGP PUBLIC KEY BLOCK-----
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
=NDqk
-----END PGP PUBLIC KEY BLOCK-----

7
public/security.txt Normal file
View File

@@ -0,0 +1,7 @@
Contact: mailto:suvankar@siliconpin.com
Contact: https://siliconpin.com/contact/
Expires: 2027-03-26T18:30:00.000Z
Encryption: https://siliconpin.com/pgp-key.txt
Acknowledgments: https://dwd.siliconpin.com
Preferred-Languages: en,bn
CSAF: https://siliconpin.com/.well-known/csaf/provider-metadata.json

72
public/sitemap.xml Normal file
View File

@@ -0,0 +1,72 @@
<?xml version="1.0" encoding="UTF-8"?>
<urlset
xmlns="http://www.sitemaps.org/schemas/sitemap/0.9"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.sitemaps.org/schemas/sitemap/0.9
http://www.sitemaps.org/schemas/sitemap/0.9/sitemap.xsd">
<!-- created with Free Online Sitemap Generator www.xml-sitemaps.com -->
<url>
<loc>https://siliconpin.com/</loc>
<lastmod>2025-03-27T14:20:53+00:00</lastmod>
<priority>1.00</priority>
</url>
<url>
<loc>https://siliconpin.com/services/</loc>
<lastmod>2025-03-27T14:20:54+00:00</lastmod>
<priority>0.80</priority>
</url>
<url>
<loc>https://siliconpin.com/contact/</loc>
<lastmod>2025-03-27T14:20:55+00:00</lastmod>
<priority>0.80</priority>
</url>
<url>
<loc>https://siliconpin.com/get-started/</loc>
<lastmod>2025-03-27T14:20:56+00:00</lastmod>
<priority>0.80</priority>
</url>
<url>
<loc>https://siliconpin.com/about-us/</loc>
<lastmod>2025-03-27T14:20:57+00:00</lastmod>
<priority>0.80</priority>
</url>
<url>
<loc>https://siliconpin.com/suggestion-or-report/</loc>
<lastmod>2025-03-27T14:20:58+00:00</lastmod>
<priority>0.80</priority>
</url>
<url>
<loc>https://siliconpin.com/hire-developer/</loc>
<lastmod>2025-03-27T14:21:00+00:00</lastmod>
<priority>0.80</priority>
</url>
<url>
<loc>https://siliconpin.com/hire-ai-agent/</loc>
<lastmod>2025-03-27T14:21:00+00:00</lastmod>
<priority>0.80</priority>
</url>
<url>
<loc>https://siliconpin.com/privacy-policy/</loc>
<lastmod>2025-03-27T14:21:02+00:00</lastmod>
<priority>0.80</priority>
</url>
<url>
<loc>https://siliconpin.com/terms-and-conditions/</loc>
<lastmod>2025-03-27T14:21:03+00:00</lastmod>
<priority>0.80</priority>
</url>
<url>
<loc>https://siliconpin.com/refund-policy/</loc>
<lastmod>2025-03-27T14:21:04+00:00</lastmod>
<priority>0.80</priority>
</url>
<url>
<loc>https://siliconpin.com/legal-agreement/</loc>
<lastmod>2025-03-27T14:21:05+00:00</lastmod>
<priority>0.80</priority>
</url>
</urlset>

View File

@@ -26,6 +26,7 @@ interface AuthResponse {
record: UserRecord;
}
const LoginPage = () => {
const [email, setEmail] = useState('suvodip@siliconpin.com');
const [password, setPassword] = useState('Simple2pass');
@@ -35,6 +36,17 @@ const LoginPage = () => {
const pb = new PocketBase("https://tst-pb.s38.siliconpin.com");
interface AuthResponse {
token: string;
record: {
query: string;
id: string;
email: string;
name: string;
avatar: string;
};
}
const handleSubmit = async (e: FormEvent) => {
e.preventDefault();
setIsLoading(true);
@@ -42,6 +54,7 @@ const LoginPage = () => {
try {
const authData = await pb.collection("users").authWithPassword(email, password);
const avatarUrl = authData.record.avatar ? pb.files.getUrl(authData.record, authData.record.avatar) : '';
const authResponse: AuthResponse = {
token: authData.token,
@@ -54,8 +67,8 @@ const LoginPage = () => {
}
};
await syncSessionWithBackend(authResponse);
// window.location.href = '/profile';
await syncSessionWithBackend(authResponse, avatarUrl);
window.location.href = '/profile';
} catch (error) {
console.error("Login failed:", error);
setStatus({
@@ -78,6 +91,7 @@ const LoginPage = () => {
throw new Error("No user record found");
}
const avatarUrl = authData.record.avatar ? pb.files.getUrl(authData.record, authData.record.avatar) : '';
const authResponse: AuthResponse = {
token: authData.token,
record: {
@@ -89,8 +103,8 @@ const LoginPage = () => {
}
};
await syncSessionWithBackend(authResponse);
// window.location.href = '/profile';
await syncSessionWithBackend(authResponse, avatarUrl);
window.location.href = '/profile';
} catch (error) {
console.error(`${provider} Login failed:`, error);
setStatus({
@@ -102,32 +116,34 @@ const LoginPage = () => {
}
};
const syncSessionWithBackend = async (authData: AuthResponse) => {
const syncSessionWithBackend = async (authData: AuthResponse, avatarUrl: string) => {
try {
const response = await fetch('http://localhost:2058/host-api/v1/users/session/', {
method: 'POST',
credentials: 'include', // Crucial for cookies
credentials: 'include', // Important for cookies
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
query: 'new',
accessToken: authData.token,
email: authData.record.email,
name: authData.record.name,
avatar: authData.record.avatar
? pb.files.getUrl(authData.record, authData.record.avatar)
: '',
avatar: avatarUrl,
isAuthenticated: true,
id: authData.record.id
})
});
if (!response.ok) throw new Error('Failed to sync session');
if (!response.ok) {
throw new Error('Failed to sync session');
}
const data = await response.json();
console.log('Session synced:', data);
console.log('Session synced with backend:', data);
} catch (error) {
console.error('Error syncing session:', error);
throw error; // Re-throw the error if you want calling functions to handle it
}
};
};
return (
<div className="min-h-screen flex items-center justify-center p-4">

View File

@@ -0,0 +1,31 @@
import React from 'react';
import '@google/model-viewer';
const ModelViewer = () => {
return (
<div className="w-full h-[500px]">
<model-viewer
src="https://bscdn.sgp1.digitaloceanspaces.com/tb-revised-books/glb/alligator.glb"
ar
ar-modes="webxr scene-viewer quick-look"
camera-controls
tone-mapping="neutral"
poster="poster.webp"
shadow-intensity="1"
style={{ width: '100%', height: '100%' }}
>
<div className="progress-bar hide" slot="progress-bar">
<div className="update-bar"></div>
</div>
<button slot="ar-button" id="ar-button">
View in your space
</button>
<div id="ar-prompt">
<img src="https://modelviewer.dev/shared-assets/icons/hand.png" />
</div>
</model-viewer>
</div>
);
};
export default ModelViewer;

View File

@@ -0,0 +1,273 @@
import React, { useEffect, useState } from "react";
interface Invoice {
invoice_id: number;
invoice_number: string;
customer_id: string;
invoice_date: string;
due_date: string;
subtotal: number;
tax_amount: number;
total_amount: number;
status: string;
payment_terms: string | null;
notes: string;
created_at: string;
updated_at: string;
}
export default function PrintInvoice() {
const [invoiceList, setInvoiceList] = useState<Invoice[]>([]);
const [error, setError] = useState<string | null>(null);
const [loading, setLoading] = useState<boolean>(true);
const [selectedInvoice, setSelectedInvoice] = useState<Invoice | null>(null);
useEffect(() => {
const getInvoiceListData = async () => {
try {
const response = await fetch('http://localhost:2058/host-api/v1/invoice/invoice-info/', {
method: 'GET',
credentials: 'include',
headers: { 'Accept': 'application/json' }
});
if (!response.ok) {
throw new Error(`HTTP error! Status: ${response.status}`);
}
const data = await response.json();
if (!data.success) {
throw new Error(data.message || 'Session fetch failed');
}
setInvoiceList(data.data);
} catch (error: any) {
console.error('Fetch error:', error);
setError(error.message);
} finally {
setLoading(false);
}
};
getInvoiceListData();
}, []);
const handlePrint = () => {
window.print();
}
const formatDate = (dateString: string) => {
return new Date(dateString).toLocaleDateString('en-US', {
year: 'numeric',
month: 'long',
day: 'numeric'
});
}
const formatCurrency = (amount: number) => {
return new Intl.NumberFormat('en-US', {
style: 'currency',
currency: 'USD'
}).format(amount);
}
const viewInvoiceDetails = (invoice: Invoice) => {
setSelectedInvoice(invoice);
}
const closeInvoiceDetails = () => {
setSelectedInvoice(null);
}
if (error) {
return <div className="p-4 text-red-600">Error: {error}</div>;
}
if (loading) {
return <div className="p-4">Loading invoice data...</div>;
}
return (
<div className="p-4 max-w-6xl mx-auto">
{/* Invoice List */}
<div className="mb-8">
<h1 className="text-2xl font-bold mb-4">Invoices</h1>
<div className="overflow-x-auto">
<table className="min-w-full bg-white border border-gray-200">
<thead className="bg-gray-50">
<tr>
<th className="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Invoice #</th>
<th className="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Date</th>
<th className="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Due Date</th>
<th className="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Amount</th>
<th className="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Status</th>
<th className="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Actions</th>
</tr>
</thead>
<tbody className="divide-y divide-gray-200">
{invoiceList.map((invoice) => (
<tr key={invoice.invoice_id} className="hover:bg-gray-50">
<td className="px-6 py-4 whitespace-nowrap">{invoice.invoice_number}</td>
<td className="px-6 py-4 whitespace-nowrap">{formatDate(invoice.invoice_date)}</td>
<td className="px-6 py-4 whitespace-nowrap">{formatDate(invoice.due_date)}</td>
<td className="px-6 py-4 whitespace-nowrap">{formatCurrency(invoice.total_amount)}</td>
<td className="px-6 py-4 whitespace-nowrap">
<span className={`px-2 inline-flex text-xs leading-5 font-semibold rounded-full
${invoice.status === 'paid' ? 'bg-green-100 text-green-800' :
invoice.status === 'draft' ? 'bg-yellow-100 text-yellow-800' :
'bg-red-100 text-red-800'}`}>
{invoice.status}
</span>
</td>
<td className="px-6 py-4 whitespace-nowrap">
<button
onClick={() => viewInvoiceDetails(invoice)}
className="text-blue-600 hover:text-blue-900 mr-2"
>
View
</button>
<button
onClick={handlePrint}
className="text-gray-600 hover:text-gray-900"
>
Print
</button>
</td>
</tr>
))}
</tbody>
</table>
</div>
</div>
{/* Invoice Detail Modal */}
{selectedInvoice && (
<div className="fixed inset-0 bg-black bg-opacity-50 flex items-center justify-center p-4 z-50">
<div className="bg-white rounded-lg shadow-xl max-w-4xl w-full max-h-[90vh] overflow-y-auto">
{/* Printable Invoice */}
<div id="printable-invoice" className="p-8">
<div className="flex justify-between items-start mb-8">
<div>
<h1 className="text-3xl font-bold text-gray-800">INVOICE</h1>
<p className="text-gray-600">{selectedInvoice.invoice_number}</p>
</div>
<div className="text-right">
<p className="text-gray-600">Status: <span className={`font-semibold
${selectedInvoice.status === 'paid' ? 'text-green-600' :
selectedInvoice.status === 'draft' ? 'text-yellow-600' :
'text-red-600'}`}>
{selectedInvoice.status.toUpperCase()}
</span></p>
<p className="text-gray-600">Date: {formatDate(selectedInvoice.invoice_date)}</p>
<p className="text-gray-600">Due: {formatDate(selectedInvoice.due_date)}</p>
</div>
</div>
<div className="mb-8">
<h2 className="text-xl font-semibold mb-2">From</h2>
<p className="text-gray-800">Your Company Name</p>
<p className="text-gray-600">123 Business Street</p>
<p className="text-gray-600">City, State 12345</p>
<p className="text-gray-600">contact@yourcompany.com</p>
</div>
<div className="mb-8">
<h2 className="text-xl font-semibold mb-2">Bill To</h2>
<p className="text-gray-800">Customer ID: {selectedInvoice.customer_id}</p>
<p className="text-gray-600">[Customer Name]</p>
<p className="text-gray-600">[Customer Address]</p>
<p className="text-gray-600">[Customer Email]</p>
</div>
<div className="border-t border-b border-gray-200 py-4 mb-6">
<div className="grid grid-cols-12 gap-4 font-semibold text-gray-700">
<div className="col-span-6">Description</div>
<div className="col-span-2 text-right">Subtotal</div>
<div className="col-span-2 text-right">Tax</div>
<div className="col-span-2 text-right">Total</div>
</div>
</div>
<div className="mb-6">
<div className="grid grid-cols-12 gap-4 mb-2">
<div className="col-span-6 text-gray-800">[Service/Product Description]</div>
<div className="col-span-2 text-right">{formatCurrency(selectedInvoice.subtotal)}</div>
<div className="col-span-2 text-right">{formatCurrency(selectedInvoice.tax_amount)}</div>
<div className="col-span-2 text-right font-semibold">{formatCurrency(selectedInvoice.total_amount)}</div>
</div>
</div>
<div className="flex justify-end mb-8">
<div className="w-64">
<div className="flex justify-between py-2 border-b border-gray-200">
<span className="font-semibold">Subtotal:</span>
<span>{formatCurrency(selectedInvoice.subtotal)}</span>
</div>
<div className="flex justify-between py-2 border-b border-gray-200">
<span className="font-semibold">Tax:</span>
<span>{formatCurrency(selectedInvoice.tax_amount)}</span>
</div>
<div className="flex justify-between py-2 font-bold text-lg">
<span>Total:</span>
<span>{formatCurrency(selectedInvoice.total_amount)}</span>
</div>
</div>
</div>
{selectedInvoice.notes && (
<div className="mb-8">
<h2 className="text-xl font-semibold mb-2">Notes</h2>
<p className="text-gray-600">{selectedInvoice.notes}</p>
</div>
)}
<div className="pt-8 border-t border-gray-200">
<p className="text-gray-600 text-center">Thank you for your business!</p>
</div>
</div>
<div className="p-4 border-t border-gray-200 flex justify-end">
<button
onClick={closeInvoiceDetails}
className="px-4 py-2 bg-gray-200 text-gray-800 rounded hover:bg-gray-300 mr-2"
>
Close
</button>
<button
onClick={handlePrint}
className="px-4 py-2 bg-blue-600 text-white rounded hover:bg-blue-700"
>
Print Invoice
</button>
</div>
</div>
</div>
)}
{/* Print Styles */}
<style>
{`
@media print {
body * {
visibility: hidden;
}
#printable-invoice, #printable-invoice * {
visibility: visible;
}
#printable-invoice {
position: absolute;
left: 0;
top: 0;
width: 100%;
padding: 20mm;
}
.no-print {
display: none !important;
}
}
`}
</style>
</div>
);
}

View File

@@ -0,0 +1,52 @@
import { useState, useRef } from 'react';
import { Button } from "./ui/button";
import { Input } from "./ui/input";
import { Label } from "./ui/label";
import { X } from "lucide-react"; // Import an icon for the remove button
export function AvatarUpload() {
const [selectedFile, setSelectedFile] = useState<File | null>(null);
const fileInputRef = useRef<HTMLInputElement>(null);
const handleFileChange = (e: React.ChangeEvent<HTMLInputElement>) => {
if (e.target.files && e.target.files.length > 0) {
setSelectedFile(e.target.files[0]);
}
};
const handleRemoveFile = () => {
setSelectedFile(null);
if (fileInputRef.current) {
fileInputRef.current.value = ''; // Reset file input
}
};
return (
<div className="space-y-4">
{!selectedFile ? (
<div className="space-y-2">
<div className="flex items-center gap-4">
<Label
htmlFor="avatar"
className="bg-primary hover:bg-primary/90 text-primary-foreground py-2 px-4 text-sm rounded-md cursor-pointer transition-colorsfocus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2">
Change Avatar
</Label>
<Input type="file" id="avatar" ref={fileInputRef} accept="image/jpeg,image/png,image/gif" className="hidden" onChange={handleFileChange}/>
<Button variant="outline" size="sm" onClick={() => fileInputRef.current?.click()}>Browse</Button>
</div>
<p className="text-xs text-muted-foreground">
JPG, GIF or PNG. 1MB max.
</p>
</div>
) : (
<div className="flex items-center justify-between p-3 space-x-2">
<div className="truncate max-w-[200px]">
<p className="text-sm font-medium truncate">{selectedFile.name}</p>
<p className="text-xs text-muted-foreground">{(selectedFile.size / 1024).toFixed(2)} KB</p>
</div>
<Button size="sm" className="text-xs p-1 h-fit">Update</Button>
<Button size="sm" onClick={handleRemoveFile} className="bg-red-500 hover:bg-red-600 text-xs p-1 h-fit">Remove</Button>
</div>
)}
</div>
);
}
export default AvatarUpload;

View File

@@ -7,6 +7,7 @@ import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue,} from ".
import { Separator } from "./ui/separator";
import { Textarea } from "./ui/textarea";
import React, { useState, useEffect } from 'react';
import UpdateAvatar from './UpdateAvatar';
interface SessionData {
[key: string]: any;
@@ -21,6 +22,7 @@ interface UserData {
export default function ProfilePage() {
const [userData, setUserData] = useState<UserData | null>(null);
const [invoiceList, setInvoiceList] = useState<any[]>([]);
const [error, setError] = useState<string | null>(null);
useEffect(() => {
@@ -45,8 +47,36 @@ export default function ProfilePage() {
throw error;
}
};
const getInvoiceListData = async () => {
try {
const response = await fetch('http://localhost:2058/host-api/v1/invoice/invoice-info/', {
method: 'GET',
credentials: 'include', // Crucial for cookies
headers: { 'Accept': 'application/json' }
});
if (!response.ok) {
throw new Error(`HTTP error! Status: ${response.status}`);
}
const data = await response.json();
if (!data.success) {
throw new Error(data.message || 'Session fetch failed');
}
setInvoiceList(data.data); // Fix: Use `data.data` instead of `data`
return data.data; // Fix: `session_data` does not exist in response
} catch (error) {
console.error('Fetch error:', error);
throw error;
}
};
fetchSessionData();
getInvoiceListData();
}, []);
if (error) {
@@ -58,12 +88,6 @@ export default function ProfilePage() {
}
return (
<div className="space-y-6 container mx-auto">
<div>
<h3 className="text-lg font-medium">Profile</h3>
<p className="text-sm text-muted-foreground">
Update your profile settings.
</p>
</div>
<Separator />
<div className="flex flex-col space-y-8 lg:flex-row lg:space-x-12 lg:space-y-0">
<div className="flex-1 lg:max-w-2xl">
@@ -80,14 +104,8 @@ export default function ProfilePage() {
<AvatarImage src={userData.session_data?.user_avatar} />
<AvatarFallback>JP</AvatarFallback>
</Avatar>
<div className="space-y-1">
<Button size="sm">
Change avatar
</Button>
<p className="text-xs text-muted-foreground">
JPG, GIF or PNG. 1MB max.
</p>
</div>
<UpdateAvatar />
</div>
<div className="grid grid-cols-1 gap-4 sm:grid-cols-2">
@@ -106,56 +124,39 @@ export default function ProfilePage() {
<Input id="email" type="email" defaultValue={userData.session_data?.user_email || ''} />
</div>
<div className="space-y-2">
<Label htmlFor="bio">Bio</Label>
<Textarea
id="bio"
defaultValue="I'm a software developer based in New York."
className="resize-none"
rows={5}
/>
</div>
</CardContent>
</Card>
<Card className="mt-6">
<CardHeader>
<CardTitle>Preferences</CardTitle>
<CardTitle>Billing Information</CardTitle>
<CardDescription>
Configure your application preferences.
View your billing history.
</CardDescription>
<table className="w-full">
<thead>
<tr>
<th className="text-left">Invoice ID</th>
<th className="text-left">Date</th>
<th className="text-left">Description</th>
<th className="text-right">Amount</th>
<th className="text-center">Action</th>
</tr>
</thead>
<tbody>
{
invoiceList.map((invoice) => (
<tr key={invoice.id}>
<td>{invoice.invoice_id}</td>
<td>{invoice.date}</td>
<td>{invoice.description}</td>
<td className="text-right">{invoice.amount}</td>
<td className="text-center"><a href="">Print</a></td>
</tr>
))
}
</tbody>
</table>
</CardHeader>
<CardContent className="space-y-6">
<div className="space-y-2">
<Label htmlFor="language">Language</Label>
<Select defaultValue="english">
<SelectTrigger id="language">
<SelectValue placeholder="Select language" />
</SelectTrigger>
<SelectContent>
<SelectItem value="english">English</SelectItem>
<SelectItem value="french">French</SelectItem>
<SelectItem value="german">German</SelectItem>
<SelectItem value="spanish">Spanish</SelectItem>
</SelectContent>
</Select>
</div>
<div className="space-y-2">
<Label htmlFor="timezone">Timezone</Label>
<Select defaultValue="est">
<SelectTrigger id="timezone">
<SelectValue placeholder="Select timezone" />
</SelectTrigger>
<SelectContent>
<SelectItem value="est">Eastern Standard Time (EST)</SelectItem>
<SelectItem value="cst">Central Standard Time (CST)</SelectItem>
<SelectItem value="mst">Mountain Standard Time (MST)</SelectItem>
<SelectItem value="pst">Pacific Standard Time (PST)</SelectItem>
</SelectContent>
</Select>
</div>
</CardContent>
</Card>
</div>
@@ -183,8 +184,7 @@ export default function ProfilePage() {
<Button className="mt-4">Update password</Button>
</CardContent>
</Card>
<Card>
<Card className="mt-6">
<CardHeader>
<CardTitle>Danger Zone</CardTitle>
<CardDescription>
@@ -193,7 +193,7 @@ export default function ProfilePage() {
</CardHeader>
<CardContent>
<div className="flex flex-col space-y-4">
<Button >Delete account</Button>
<Button className="bg-red-500 hover:bg-red-600">Delete account</Button>
<Button variant="outline">Export data</Button>
</div>
</CardContent>

View File

@@ -0,0 +1,16 @@
import React from 'react';
interface ContainerProps {
children: React.ReactNode;
className?: string;
}
const Container: React.FC<ContainerProps> = ({ children, className = '' }) => {
return (
<div className={`max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 ${className}`}>
{children}
</div>
);
};
export default Container;

View File

@@ -0,0 +1,38 @@
interface TableProps {
headers: string[];
data: any[];
className?: string;
children?: React.ReactNode; // Add children here
}
const Table: React.FC<TableProps> = ({ headers, data, className = '', children }) => {
return (
<div className={`overflow-x-auto ${className}`}>
<table className="min-w-full bg-white border border-gray-300">
<thead>
<tr>
{headers.map((header, index) => (
<th key={index} className="px-6 py-3 text-left text-sm font-medium text-gray-600">
{header}
</th>
))}
</tr>
</thead>
<tbody>
{data.map((row, index) => (
<tr key={index} className="border-t">
{Object.values(row).map((value, idx) => (
<td key={idx} className="px-6 py-4 text-sm text-gray-800">
{value as React.ReactNode}
</td>
))}
</tr>
))}
{children} {/* This will allow you to pass additional content */}
</tbody>
</table>
</div>
);
};
export default Table;

View File

@@ -0,0 +1,42 @@
import type { ElementType } from 'react';
interface TypographyProps {
variant: 'h1' | 'h2' | 'h3' | 'h4' | 'h5' | 'h6' | 'body1' | 'body2' | 'caption';
children: React.ReactNode;
className?: string;
color?: string; // Add color prop
}
const Typography: React.FC<TypographyProps> = ({ variant, children, className = '', color }) => {
const variantStyles: Record<string, React.CSSProperties> = {
h1: { fontSize: '2.5rem', fontWeight: 'bold' },
h2: { fontSize: '2rem', fontWeight: 'bold' },
h3: { fontSize: '1.75rem', fontWeight: 'bold' },
h4: { fontSize: '1.5rem', fontWeight: 'bold' },
h5: { fontSize: '1.25rem', fontWeight: 'bold' },
h6: { fontSize: '1rem', fontWeight: 'bold' },
body1: { fontSize: '1rem', fontWeight: 'normal' },
body2: { fontSize: '0.875rem', fontWeight: 'normal' },
caption: { fontSize: '0.75rem', fontWeight: 'normal' },
};
const combinedStyle = { ...variantStyles[variant], ...(color && { color }), ...(className ? { className } : {}) };
const variantToTag: Record<string, ElementType> = {
h1: 'h1',
h2: 'h2',
h3: 'h3',
h4: 'h4',
h5: 'h5',
h6: 'h6',
body1: 'p',
body2: 'p',
caption: 'span',
};
const Tag: ElementType = variantToTag[variant];
return <Tag style={combinedStyle}>{children}</Tag>;
};
export default Typography;

11
src/pages/3d-model.astro Normal file
View File

@@ -0,0 +1,11 @@
---
import Layout from "../layouts/Layout.astro";
import ModelViewer from "../components/ModelViewer"; // Ensure the name matches
---
<Layout title="3D Model Viewer">
<div class="flex flex-col items-center justify-center h-screen">
<h1 class="text-4xl font-bold mb-4">3D Model Viewer</h1>
<ModelViewer client:only="react" />
</div>
</Layout>

View File

@@ -0,0 +1,7 @@
---
import Layout from "../layouts/Layout.astro";
import Invoice from "../components/PrintInvoice";
---
<Layout title="">
<Invoice client:load/>
</Layout>

3814
yarn.lock Normal file

File diff suppressed because it is too large Load Diff