24 Commits

Author SHA1 Message Date
ee2bbc817f Merge pull request 's11' (#29) from tmp3 into main
Reviewed-on: #29
2025-04-04 14:29:21 +00:00
Suvodip
4b2656cea6 s11 2025-04-04 19:58:51 +05:30
6b7e7fcd6d Merge pull request 's1' (#28) from tmp3 into main
Reviewed-on: #28
2025-04-04 13:52:47 +00:00
Suvodip
96a027dafb s1 2025-04-04 19:21:24 +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
Suvodip
c3faedf6f8 s11 2025-03-27 20:33:15 +05:30
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
026c6c34b2 Merge pull request 'get-started-logic' (#14) from get-started-logic into staging
Reviewed-on: #14
2025-03-27 13:50:35 +00:00
Suvodip
beabcc7b67 s1 2025-03-27 19:19:47 +05:30
734fed06b8 norms 2025-03-27 19:14:59 +05:30
Suvodip
d2f3576d10 add some php host api and add hire developer and ai agent page 2025-03-26 19:58:45 +05:30
63c973f1ca t 2025-03-26 12:30:02 +05:30
5c50e77a1c Merge pull request 'add domains flder and functionality' (#7) from tpm1 into staging
Reviewed-on: #7
2025-03-25 15:07:41 +00:00
f7561801b7 t 2025-03-25 14:23:34 +05:30
61 changed files with 8442 additions and 90 deletions

BIN
dist.zip

Binary file not shown.

946
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@@ -12,14 +12,26 @@
"dependencies": {
"@astrojs/react": "^4.2.1",
"@astrojs/tailwind": "^6.0.0",
"@radix-ui/react-dialog": "^1.1.6",
"@radix-ui/react-select": "^2.1.6",
"@radix-ui/react-tabs": "^1.1.3",
"@radix-ui/react-toast": "^1.2.6",
"@shadcn/ui": "^0.0.4",
"@types/date-fns": "^2.5.3",
"@types/react": "^19.0.12",
"astro": "^5.5.2",
"autoprefixer": "^10.4.21",
"class-variance-authority": "^0.7.1",
"clsx": "^2.1.1",
"date-fns": "^4.1.0",
"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": "^3.4.17",
"tailwindcss-animate": "^1.0.7"
},
"packageManager": "yarn@1.22.22+sha512.a6b2f7906b721bba3d67d4aff083df04dad64c399707841b7acf00f6b133b7ac24255f2652fa22ae3534329dc6180534e98d17432037ff6fd140556e2bb3137e"
}

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,39 @@
{
"csaf_version": "2.0",
"provider": {
"name": "DWD Consultancy Services",
"namespace": "com.siliconpin.dwd",
"contact_details": [
{
"name": "Suvankar Sarkar",
"email": "suvankar@siliconpin.com",
"phone": "+91-700-160-1485",
"website": "https://dwd.siliconpin.com/#about"
}
],
"publisher": {
"type": "vendor",
"name": "SiliconPin",
"namespace": "com.siliconpin"
},
"distribution": {
"tlp_label": "WHITE",
"url": "https://dwd.siliconpin.com/#about",
"pgp_key": "https://siliconpin.com/pgp-key.asc"
},
"tracking": {
"id": "EX-2025-0001",
"status": "final",
"initial_release_date": "2025-03-27T12:00:00Z",
"current_release_date": "2025-03-27T12:00:00Z",
"revision_history": [
{
"number": "1.0",
"date": "2025-03-27T12:00:00Z",
"summary": "Initial release"
}
]
}
}
}

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

39
public/advisory.json Normal file
View File

@@ -0,0 +1,39 @@
{
"csaf_version": "2.0",
"provider": {
"name": "DWD Consultancy Services",
"namespace": "com.siliconpin.dwd",
"contact_details": [
{
"name": "Suvankar Sarkar",
"email": "suvankar@siliconpin.com",
"phone": "+91-700-160-1485",
"website": "https://dwd.siliconpin.com/#about"
}
],
"publisher": {
"type": "vendor",
"name": "SiliconPin",
"namespace": "com.siliconpin"
},
"distribution": {
"tlp_label": "WHITE",
"url": "https://dwd.siliconpin.com/#about",
"pgp_key": "https://siliconpin.com/pgp-key.asc"
},
"tracking": {
"id": "EX-2025-0001",
"status": "final",
"initial_release_date": "2025-03-27T12:00:00Z",
"current_release_date": "2025-03-27T12:00:00Z",
"revision_history": [
{
"number": "1.0",
"date": "2025-03-27T12:00:00Z",
"summary": "Initial release"
}
]
}
}
}

14
public/advisory.json.asc Normal file
View File

@@ -0,0 +1,14 @@
-----BEGIN PGP SIGNATURE-----
iQGzBAABCAAdFiEEL0GUnJpZXELqjphXMaCR/T68rscFAmflVEgACgkQMaCR/T68
rse45Av6A0YL6qJSBvqN6tty7j0G68yfgsjeJxtuCxXiDwUjUIU4VwHORNeddZD/
Lx5nmYf4BgszoFjgaix8dyu87bDn2QaA13+2/GMDkc3GT8yW8bzaR/saV1J1v070
wVhButTIM48c4NpuqFX5Kytcvm632lZdeKBFF2rFfdZ3ajd7IJMr7CYVzziJdVa8
pR00udgBYE05ewV9W8FLMmTwWqoOIQYtYR3+YP1rhuYInZ/7dobO5aI4/eliHIBo
xe93UET9Zo19ywbnqhas+x4wJl8roI2qWDYsjp3+gRG4Ns5Myf2puhtVxexiY4xK
6cvra2ujOFLksykPgy5lpw2WcpFFnm9K2s9NMvLAsv+3OXZHQANL7vS5ZFXxNxyU
fm+F5uP8LiR1IXpFcJAP3MweaDdlWhL7TZp1tuA0J/i/oCFShZypzQLb9welYVbH
a/CPm1pavGxEIlFkSOAlMmcp945s41qYia2BFOlW1ZjsDkrAR2561aLI/O7klpNw
VLnngQsi
=s4sX
-----END PGP SIGNATURE-----

1
public/assets/clock.svg Normal file
View File

@@ -0,0 +1 @@
<svg width="15px" height="15px" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg"><g id="SVGRepo_bgCarrier" stroke-width="0"></g><g id="SVGRepo_tracerCarrier" stroke-linecap="round" stroke-linejoin="round"></g><g id="SVGRepo_iconCarrier"> <path d="M12 8V12L15 15" stroke="#6d9e37" stroke-width="2" stroke-linecap="round"></path> <circle cx="12" cy="12" r="9" stroke="#6d9e37" stroke-width="2"></circle> </g></svg>

After

Width:  |  Height:  |  Size: 430 B

View File

@@ -0,0 +1 @@
<svg width="30px" height="30px" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg"><g id="SVGRepo_bgCarrier" stroke-width="0"></g><g id="SVGRepo_tracerCarrier" stroke-linecap="round" stroke-linejoin="round"></g><g id="SVGRepo_iconCarrier"> <path d="M4.4955 7.44088C3.54724 8.11787 2.77843 8.84176 2.1893 9.47978C0.857392 10.9222 0.857393 13.0778 2.1893 14.5202C3.9167 16.391 7.18879 19 12 19C13.2958 19 14.4799 18.8108 15.5523 18.4977L13.8895 16.8349C13.2936 16.9409 12.6638 17 12 17C7.9669 17 5.18832 14.82 3.65868 13.1634C3.03426 12.4872 3.03426 11.5128 3.65868 10.8366C4.23754 10.2097 4.99526 9.50784 5.93214 8.87753L4.4955 7.44088Z" fill="#6d9e37"></path> <path d="M8.53299 11.4784C8.50756 11.6486 8.49439 11.8227 8.49439 12C8.49439 13.933 10.0614 15.5 11.9944 15.5C12.1716 15.5 12.3458 15.4868 12.516 15.4614L8.53299 11.4784Z" fill="#6d9e37"></path> <path d="M15.4661 12.4471L11.5473 8.52829C11.6937 8.50962 11.8429 8.5 11.9944 8.5C13.9274 8.5 15.4944 10.067 15.4944 12C15.4944 12.1515 15.4848 12.3007 15.4661 12.4471Z" fill="#6d9e37"></path> <path d="M18.1118 15.0928C19.0284 14.4702 19.7715 13.7805 20.3413 13.1634C20.9657 12.4872 20.9657 11.5128 20.3413 10.8366C18.8117 9.18002 16.0331 7 12 7C11.3594 7 10.7505 7.05499 10.1732 7.15415L8.50483 5.48582C9.5621 5.1826 10.7272 5 12 5C16.8112 5 20.0833 7.60905 21.8107 9.47978C23.1426 10.9222 23.1426 13.0778 21.8107 14.5202C21.2305 15.1486 20.476 15.8603 19.5474 16.5284L18.1118 15.0928Z" fill="#6d9e37"></path> <path d="M2.00789 3.42207C1.61736 3.03155 1.61736 2.39838 2.00789 2.00786C2.39841 1.61733 3.03158 1.61733 3.4221 2.00786L22.0004 20.5862C22.391 20.9767 22.391 21.6099 22.0004 22.0004C21.6099 22.3909 20.9767 22.3909 20.5862 22.0004L2.00789 3.42207Z" fill="#6d9e37"></path> </g></svg>

After

Width:  |  Height:  |  Size: 1.7 KiB

1
public/assets/eye.svg Normal file
View File

@@ -0,0 +1 @@
<svg width="30px" height="30px" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg"><g id="SVGRepo_bgCarrier" stroke-width="0"></g><g id="SVGRepo_tracerCarrier" stroke-linecap="round" stroke-linejoin="round"></g><g id="SVGRepo_iconCarrier"> <path fill-rule="evenodd" clip-rule="evenodd" d="M11.9944 15.5C13.9274 15.5 15.4944 13.933 15.4944 12C15.4944 10.067 13.9274 8.5 11.9944 8.5C10.0614 8.5 8.49439 10.067 8.49439 12C8.49439 13.933 10.0614 15.5 11.9944 15.5ZM11.9944 13.4944C11.1691 13.4944 10.5 12.8253 10.5 12C10.5 11.1747 11.1691 10.5056 11.9944 10.5056C12.8197 10.5056 13.4888 11.1747 13.4888 12C13.4888 12.8253 12.8197 13.4944 11.9944 13.4944Z" fill="#6d9e37"></path> <path fill-rule="evenodd" clip-rule="evenodd" d="M12 5C7.18879 5 3.9167 7.60905 2.1893 9.47978C0.857392 10.9222 0.857393 13.0778 2.1893 14.5202C3.9167 16.391 7.18879 19 12 19C16.8112 19 20.0833 16.391 21.8107 14.5202C23.1426 13.0778 23.1426 10.9222 21.8107 9.47978C20.0833 7.60905 16.8112 5 12 5ZM3.65868 10.8366C5.18832 9.18002 7.9669 7 12 7C16.0331 7 18.8117 9.18002 20.3413 10.8366C20.9657 11.5128 20.9657 12.4872 20.3413 13.1634C18.8117 14.82 16.0331 17 12 17C7.9669 17 5.18832 14.82 3.65868 13.1634C3.03426 12.4872 3.03426 11.5128 3.65868 10.8366Z" fill="#6d9e37"></path> </g></svg>

After

Width:  |  Height:  |  Size: 1.2 KiB

View File

@@ -0,0 +1 @@
<svg width="40px" height="40px" viewBox="0 0 32 32" fill="none" xmlns="http://www.w3.org/2000/svg"><g id="SVGRepo_bgCarrier" stroke-width="0"></g><g id="SVGRepo_tracerCarrier" stroke-linecap="round" stroke-linejoin="round"></g><g id="SVGRepo_iconCarrier"> <circle cx="16" cy="16" r="14" fill="url(#paint0_linear_87_7208)"></circle> <path d="M21.2137 20.2816L21.8356 16.3301H17.9452V13.767C17.9452 12.6857 18.4877 11.6311 20.2302 11.6311H22V8.26699C22 8.26699 20.3945 8 18.8603 8C15.6548 8 13.5617 9.89294 13.5617 13.3184V16.3301H10V20.2816H13.5617V29.8345C14.2767 29.944 15.0082 30 15.7534 30C16.4986 30 17.2302 29.944 17.9452 29.8345V20.2816H21.2137Z" fill="white"></path> <defs> <linearGradient id="paint0_linear_87_7208" x1="16" y1="2" x2="16" y2="29.917" gradientUnits="userSpaceOnUse"> <stop stop-color="#18ACFE"></stop> <stop offset="1" stop-color="#0163E0"></stop> </linearGradient> </defs> </g></svg>

After

Width:  |  Height:  |  Size: 909 B

1
public/assets/github.svg Normal file
View File

@@ -0,0 +1 @@
<svg width="40px" height="40px" viewBox="-1.6 -1.6 19.20 19.20" xmlns="http://www.w3.org/2000/svg" fill="#000000" class="bi bi-github"><g id="SVGRepo_bgCarrier" stroke-width="0" transform="translate(1.3600000000000003,1.3600000000000003), scale(0.83)"><rect x="-1.6" y="-1.6" width="19.20" height="19.20" rx="9.6" fill="#ffffff" strokewidth="0"></rect></g><g id="SVGRepo_tracerCarrier" stroke-linecap="round" stroke-linejoin="round"></g><g id="SVGRepo_iconCarrier"> <path d="M8 0C3.58 0 0 3.58 0 8c0 3.54 2.29 6.53 5.47 7.59.4.07.55-.17.55-.38 0-.19-.01-.82-.01-1.49-2.01.37-2.53-.49-2.69-.94-.09-.23-.48-.94-.82-1.13-.28-.15-.68-.52-.01-.53.63-.01 1.08.58 1.23.82.72 1.21 1.87.87 2.33.66.07-.52.28-.87.51-1.07-1.78-.2-3.64-.89-3.64-3.95 0-.87.31-1.59.82-2.15-.08-.2-.36-1.02.08-2.12 0 0 .67-.21 2.2.82.64-.18 1.32-.27 2-.27.68 0 1.36.09 2 .27 1.53-1.04 2.2-.82 2.2-.82.44 1.1.16 1.92.08 2.12.51.56.82 1.27.82 2.15 0 3.07-1.87 3.75-3.65 3.95.29.25.54.73.54 1.48 0 1.07-.01 1.93-.01 2.2 0 .21.15.46.55.38A8.012 8.012 0 0 0 16 8c0-4.42-3.58-8-8-8z"></path> </g></svg>

After

Width:  |  Height:  |  Size: 1.0 KiB

1
public/assets/google.svg Normal file
View File

@@ -0,0 +1 @@
<svg width="40px" height="40px" viewBox="0 0 16 16" xmlns="http://www.w3.org/2000/svg" fill="none"><g id="SVGRepo_bgCarrier" stroke-width="0"></g><g id="SVGRepo_tracerCarrier" stroke-linecap="round" stroke-linejoin="round"></g><g id="SVGRepo_iconCarrier"><path fill="#4285F4" d="M14.9 8.161c0-.476-.039-.954-.121-1.422h-6.64v2.695h3.802a3.24 3.24 0 01-1.407 2.127v1.75h2.269c1.332-1.22 2.097-3.02 2.097-5.15z"></path><path fill="#34A853" d="M8.14 15c1.898 0 3.499-.62 4.665-1.69l-2.268-1.749c-.631.427-1.446.669-2.395.669-1.836 0-3.393-1.232-3.952-2.888H1.85v1.803A7.044 7.044 0 008.14 15z"></path><path fill="#FBBC04" d="M4.187 9.342a4.17 4.17 0 010-2.68V4.859H1.849a6.97 6.97 0 000 6.286l2.338-1.803z"></path><path fill="#EA4335" d="M8.14 3.77a3.837 3.837 0 012.7 1.05l2.01-1.999a6.786 6.786 0 00-4.71-1.82 7.042 7.042 0 00-6.29 3.858L4.186 6.66c.556-1.658 2.116-2.89 3.952-2.89z"></path></g></svg>

After

Width:  |  Height:  |  Size: 901 B

1
public/assets/send.svg Normal file
View File

@@ -0,0 +1 @@
<svg width="30px" height="30px" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg" stroke="#ffffff"><g id="SVGRepo_bgCarrier" stroke-width="0"></g><g id="SVGRepo_tracerCarrier" stroke-linecap="round" stroke-linejoin="round"></g><g id="SVGRepo_iconCarrier"> <path d="M20 4L3 9.31372L10.5 13.5M20 4L14.5 21L10.5 13.5M20 4L10.5 13.5" stroke="#ffffff" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"></path> </g></svg>

After

Width:  |  Height:  |  Size: 446 B

1
public/humans.txt Normal file
View File

@@ -0,0 +1 @@
SiliconPin is a Decentralized NonProfit Organization / Group, Creating some digital freedom, if you want to join -welcome.

41
public/pgp-key.txt Normal file
View File

@@ -0,0 +1,41 @@
-----BEGIN PGP PUBLIC KEY BLOCK-----
mQGNBGflUDcBDADbKdXMyHvxChQ2TVdNx0vemulONqiPeun2kL2PKCQ/U+us/S2i
P6JMlGIdQVzhp90R8JnLV/knydT/lPKmyKqwl1TUc+Z2oE7QtafF+E1OiF264709
9gY0d9LH9PTzsO+3456QfeT/N1HrLJcwZ79EDBa8uIwx3YFCmhzosEoNpeFFSKbr
Q74dG2WK6wXwr9xTyWjSfxSdpPTXz1DaxdzGzndmHuS87mlZM39uCdv5+x5lT66D
KmESaf1jXXTJ1SVcXO0rkwUzPeRjrFN2P0XoIhrW1zhEUIW6aV5NLBdnr0nS+WRM
LZRcfB+GLFo41eP4PNfcHAXpj7Tbn3ewqdiqS4zvpZ8EQH4pQqMU/IflEcF0zKqT
KiBqtOQL2WoI82S+pkhchMH/r+6oBzvbQK5tluQwb4+1n/tHbI3Sm+f5Rnd012vO
Iz87Hh1dj1Iaq642xaP85H4nLQseMKy8aTKyLtRXCm71jj5IffUYp+XUokF9nkwY
lGwssQ5dBq47M5EAEQEAAbQvc3V2YW5rYXIgc2Fya2FyIChLYXIpIDxzdXZhbmth
ckBzaWxpY29ucGluLmNvbT6JAdcEEwEIAEEWIQQvQZScmllcQuqOmFcxoJH9Pryu
xwUCZ+VQNwIbAwUJA8JnAAULCQgHAgIiAgYVCgkICwIEFgIDAQIeBwIXgAAKCRAx
oJH9Pryux0XlC/4xs1nfC5momwL24UhoTHPhvkNUN0vufJLPAgum71Pbe7gNYwMp
pRGzfVAlYkLB7O+JfPDcWbOSm8/fi1274eeAJOu8BhbLH4zPKZlXSXM1C2R5dKat
0ZuSO4q1a7vsrhlsDVzbgLq75la5+LjJRicRTIPEqhoX56tcwHUc0YlDLW7wViB3
xWXGemr27anqFG5sb41rqKoMgBOIcK3M9t4qgqut3WFpiIfwF9mRaI7j2yq/IlGQ
NwR+af/AttETyIT3QWxQP+zT3JMfV8++WbniI6f/ywbHlvcx5JIvoHQSKAofYJdY
O0uECg2E158Q+zvRsmFaAjudyCcKPc/9YOCQBj/N4u++INuqHVNAY5KO2IyPhB09
Q+J+N1UVbsZRB5JC5oMuCntDeATcjSlyVMgj5gaBjA1loUOjj0lc+gs4zY6cMwPe
747WsS6DE2L4C8mz1PlDacK/BhR7dle4aA8CCr4MJfsdz3zzSHs0afaVRQHNf9ac
jIiF5wvmCpSztAS5AY0EZ+VQNwEMANQ/lyRGDmC6vWWPckh04N+BnTWLff+iuMxp
LdU4h8843BEB7RdNcynwPEGV3CP9H/NjKp6kzybKzTOaGqONtuwYTTMfHQEHzdrL
E2afN3fAxPQ6e1/Bqdsz4F9z3UIKVTIJjc5CGoFeHINgCGtGbWvD9HDeHQuH/rO0
4lQjUpJtl8sbaLsBIiMabwPHnabjYvZ3E8knUuqbifaIsdPuFoqQvC0Uy3z7CaU3
qAER7sVdY9NjjiD20lG8lLivKGYTq9QXZFHJz2DLvW8aP+HY+G/kIjuw8MwGPg38
55FkR0pT8L5lJvSUDEDzMTd9i1TUImeRqw/F9AYK9dsXLPxNZ7EcMwRs/6nUjAkh
04tfHJCPwn9HeMtLFrQrg3NfhBIV0/wp8lp8lxCsgd8Cwc/lH3bU/KHdzZQwHPNi
i5F378AlrTlWoSBq5/3rkJRWPU7DavwGJwRnmFCcx1iqyfZB80/G+N0WMbL8gZjY
JoLKoytdLNWS/wlGTx2DIb4E3sStlwARAQABiQG8BBgBCAAmFiEEL0GUnJpZXELq
jphXMaCR/T68rscFAmflUDcCGwwFCQPCZwAACgkQMaCR/T68rse+7wv8CJFMnAGH
hjedAXCODUztEtak8fZkHTiJBow3tx9XxYjnQe99zhZOswBJ8CF7cakpTXMYtWRf
yA30Qf1YNwYeb5WwIG1nsep2bQzeC/LP1pu7XDmRHyI+kakhnEul/27kPHNubSIr
9h0MXtTxRFkde27DD+QzJoqvUKCNtpyhKJY7jv6+9UfpSNIZ6FmpN9hxQRv6oFgn
uoFCKjfUxhGsN2j0DxHF+WJjQ+jBcRzpORD4TfAjqej++OdUv/3NR88UMxima26H
PMZP7chiojFzMAPLniYEFXMgh+l7Z+ssdWbCxfPQq90DoZ/Vylnfmq7E9pjE54Dr
PhIcMfEYlH+erdKrHgCaCzew6Y9kNHPVTuKqWA0/105J7+umk/5Z84eVfmWzJysQ
30KHyfM5lMe1Eb++QECfzknD9xQvAb/YoVT0yHPUhCExzVZUc6InmhwvkX+nilk4
zCPxkkNCwSX1LKNkCt9dW09MLJ2Gyf3r+goOqpx7mcs5g7HQGwc9u2Vp
=NDqk
-----END PGP PUBLIC KEY BLOCK-----

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

@@ -0,0 +1,41 @@
-----BEGIN PGP PUBLIC KEY BLOCK-----
mQGNBGflUDcBDADbKdXMyHvxChQ2TVdNx0vemulONqiPeun2kL2PKCQ/U+us/S2i
P6JMlGIdQVzhp90R8JnLV/knydT/lPKmyKqwl1TUc+Z2oE7QtafF+E1OiF264709
9gY0d9LH9PTzsO+3456QfeT/N1HrLJcwZ79EDBa8uIwx3YFCmhzosEoNpeFFSKbr
Q74dG2WK6wXwr9xTyWjSfxSdpPTXz1DaxdzGzndmHuS87mlZM39uCdv5+x5lT66D
KmESaf1jXXTJ1SVcXO0rkwUzPeRjrFN2P0XoIhrW1zhEUIW6aV5NLBdnr0nS+WRM
LZRcfB+GLFo41eP4PNfcHAXpj7Tbn3ewqdiqS4zvpZ8EQH4pQqMU/IflEcF0zKqT
KiBqtOQL2WoI82S+pkhchMH/r+6oBzvbQK5tluQwb4+1n/tHbI3Sm+f5Rnd012vO
Iz87Hh1dj1Iaq642xaP85H4nLQseMKy8aTKyLtRXCm71jj5IffUYp+XUokF9nkwY
lGwssQ5dBq47M5EAEQEAAbQvc3V2YW5rYXIgc2Fya2FyIChLYXIpIDxzdXZhbmth
ckBzaWxpY29ucGluLmNvbT6JAdcEEwEIAEEWIQQvQZScmllcQuqOmFcxoJH9Pryu
xwUCZ+VQNwIbAwUJA8JnAAULCQgHAgIiAgYVCgkICwIEFgIDAQIeBwIXgAAKCRAx
oJH9Pryux0XlC/4xs1nfC5momwL24UhoTHPhvkNUN0vufJLPAgum71Pbe7gNYwMp
pRGzfVAlYkLB7O+JfPDcWbOSm8/fi1274eeAJOu8BhbLH4zPKZlXSXM1C2R5dKat
0ZuSO4q1a7vsrhlsDVzbgLq75la5+LjJRicRTIPEqhoX56tcwHUc0YlDLW7wViB3
xWXGemr27anqFG5sb41rqKoMgBOIcK3M9t4qgqut3WFpiIfwF9mRaI7j2yq/IlGQ
NwR+af/AttETyIT3QWxQP+zT3JMfV8++WbniI6f/ywbHlvcx5JIvoHQSKAofYJdY
O0uECg2E158Q+zvRsmFaAjudyCcKPc/9YOCQBj/N4u++INuqHVNAY5KO2IyPhB09
Q+J+N1UVbsZRB5JC5oMuCntDeATcjSlyVMgj5gaBjA1loUOjj0lc+gs4zY6cMwPe
747WsS6DE2L4C8mz1PlDacK/BhR7dle4aA8CCr4MJfsdz3zzSHs0afaVRQHNf9ac
jIiF5wvmCpSztAS5AY0EZ+VQNwEMANQ/lyRGDmC6vWWPckh04N+BnTWLff+iuMxp
LdU4h8843BEB7RdNcynwPEGV3CP9H/NjKp6kzybKzTOaGqONtuwYTTMfHQEHzdrL
E2afN3fAxPQ6e1/Bqdsz4F9z3UIKVTIJjc5CGoFeHINgCGtGbWvD9HDeHQuH/rO0
4lQjUpJtl8sbaLsBIiMabwPHnabjYvZ3E8knUuqbifaIsdPuFoqQvC0Uy3z7CaU3
qAER7sVdY9NjjiD20lG8lLivKGYTq9QXZFHJz2DLvW8aP+HY+G/kIjuw8MwGPg38
55FkR0pT8L5lJvSUDEDzMTd9i1TUImeRqw/F9AYK9dsXLPxNZ7EcMwRs/6nUjAkh
04tfHJCPwn9HeMtLFrQrg3NfhBIV0/wp8lp8lxCsgd8Cwc/lH3bU/KHdzZQwHPNi
i5F378AlrTlWoSBq5/3rkJRWPU7DavwGJwRnmFCcx1iqyfZB80/G+N0WMbL8gZjY
JoLKoytdLNWS/wlGTx2DIb4E3sStlwARAQABiQG8BBgBCAAmFiEEL0GUnJpZXELq
jphXMaCR/T68rscFAmflUDcCGwwFCQPCZwAACgkQMaCR/T68rse+7wv8CJFMnAGH
hjedAXCODUztEtak8fZkHTiJBow3tx9XxYjnQe99zhZOswBJ8CF7cakpTXMYtWRf
yA30Qf1YNwYeb5WwIG1nsep2bQzeC/LP1pu7XDmRHyI+kakhnEul/27kPHNubSIr
9h0MXtTxRFkde27DD+QzJoqvUKCNtpyhKJY7jv6+9UfpSNIZ6FmpN9hxQRv6oFgn
uoFCKjfUxhGsN2j0DxHF+WJjQ+jBcRzpORD4TfAjqej++OdUv/3NR88UMxima26H
PMZP7chiojFzMAPLniYEFXMgh+l7Z+ssdWbCxfPQq90DoZ/Vylnfmq7E9pjE54Dr
PhIcMfEYlH+erdKrHgCaCzew6Y9kNHPVTuKqWA0/105J7+umk/5Z84eVfmWzJysQ
30KHyfM5lMe1Eb++QECfzknD9xQvAb/YoVT0yHPUhCExzVZUc6InmhwvkX+nilk4
zCPxkkNCwSX1LKNkCt9dW09MLJ2Gyf3r+goOqpx7mcs5g7HQGwc9u2Vp
=NDqk
-----END PGP PUBLIC KEY BLOCK-----

7
public/robots.txt Normal file
View File

@@ -0,0 +1,7 @@
User-agent: *
Disallow: /secret-location/
User-agent: *
Allow: /
Sitemap: https://siliconpin.com/sitemap.xml

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

@@ -18,7 +18,7 @@ export const DomainSetupForm = ({ defaultSubdomain }) => {
const [customDomain, setCustomDomain] = useState('');
const [customSubdomain, setCustomSubdomain] = useState('');
const [domainType, setDomainType] = useState('domain'); // 'domain' or 'subdomain'
// Domain validation states
const [isValidating, setIsValidating] = useState(false);
const [isValidDomain, setIsValidDomain] = useState(false);
@@ -43,15 +43,6 @@ export const DomainSetupForm = ({ defaultSubdomain }) => {
// File upload reference
const fileInputRef = React.useRef(null);
// Function to clean domain input
const cleanDomainInput = (input) => {
// Remove http://, https://, www., and trailing slashes
return input
.replace(/^(https?:\/\/)?(www\.)?/i, '')
.replace(/\/+$/, '')
.trim();
};
// Effect for handling domain type changes
useEffect(() => {
if (!useCustomDomain) {
@@ -140,12 +131,12 @@ export const DomainSetupForm = ({ defaultSubdomain }) => {
// Handle domain and subdomain input changes
const handleDomainChange = (e) => {
const cleanedValue = cleanDomainInput(e.target.value);
const cleanedValue = e.target.value.replace(/^(https?:\/\/)?(www\.)?/i, '').replace(/\/+$/, '').trim();
setCustomDomain(cleanedValue);
};
const handleSubdomainChange = (e) => {
const cleanedValue = cleanDomainInput(e.target.value);
const cleanedValue = e.target.value.replace(/^(https?:\/\/)?/i, '').replace(/\/+$/, '').trim();
setCustomSubdomain(cleanedValue);
};
@@ -192,6 +183,7 @@ export const DomainSetupForm = ({ defaultSubdomain }) => {
// Simulate an API call to validate the domain
setTimeout(() => {
// Simulate a real domain check - in a real app this would be an API call
// call /host-api/v1/domains/validate/?domain=domain.com
const checkResult = true; // Assume domain is valid for demo
setIsValidating(false);
@@ -205,10 +197,42 @@ export const DomainSetupForm = ({ defaultSubdomain }) => {
}
validateForm();
}, 1500);
}, 500);
};
// Check DNS configuration
const checkSubDomainCname = () => {
const domainToCheck = customDomain || customSubdomain;
fetch('http://localhost:2058/host-api/v1/check-c-name/', {
method: 'POST',
headers: {
'Content-Type': 'application/x-www-form-urlencoded',
},
body: `domain=${encodeURIComponent(domainToCheck)}`
})
.then(response => {
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`);
}
return response.json();
})
.then(data => {
if (data.status === 'success') {
checkDnsConfig('cname');
showToast(`Checking ${type}... (This would verify DNS in a real app)`);
console.log('CNAME record:', data.cname);
// Handle success - update UI
} else {
console.error('Error:', data.message);
// Handle error
}
})
.catch(error => {
console.error('Fetch error:', error);
// Show error to user
});
};
const checkDnsConfig = (type) => {
showToast(`Checking ${type}... (This would verify DNS in a real app)`);
@@ -543,10 +567,10 @@ export const DomainSetupForm = ({ defaultSubdomain }) => {
{/* Domain Validation */}
<button
disabled={!customDomain}
disabled={!customDomain && !customSubdomain}
type="button"
onClick={validateDomain}
className={`px-4 py-2 ${!customDomain ? 'bg-neutral-600 cursor-not-allowed' : 'bg-[#6d9e37] focus:ring-[#6d9e37] transition-colors'} text-white font-medium rounded-md transition-colors focus:outline-none`}
className={`px-4 py-2 ${ !customDomain && !customSubdomain ? 'bg-neutral-600 cursor-not-allowed' : 'bg-[#6d9e37] focus:ring-[#6d9e37] transition-colors'} text-white font-medium rounded-md transition-colors focus:outline-none`}
>
Validate Domain
</button>
@@ -606,7 +630,7 @@ export const DomainSetupForm = ({ defaultSubdomain }) => {
<div className="mt-2 text-right">
<button
type="button"
onClick={() => checkDnsConfig('cname')}
onClick={() => (checkSubDomainCname())}
className={`px-3 py-1 text-white text-sm rounded
${dnsVerified.cname
? 'bg-green-700 hover:bg-green-600'

View File

@@ -0,0 +1,333 @@
"use client";
import { useState } from "react";
import { Card, CardContent, CardDescription, CardFooter, CardHeader, CardTitle } from "./ui/card";
import { Button } from "./ui/button";
import { Dialog, DialogContent, DialogDescription, DialogFooter, DialogHeader, DialogTitle } from "./ui/dialog";
import { Input } from "./ui/input";
// Define the AI Agent type
interface AIAgent {
id: number;
title: string;
description: string;
capabilities: string[];
responseTime: string;
pricing: string;
useCases: string[];
}
// AI Agent types data
const aiAgentTypes: AIAgent[] = [
{
id: 1,
title: "Code Assistant",
description: "AI-powered coding assistant that helps write, debug, and optimize code across multiple languages",
capabilities: ["Code Generation", "Code Review", "Bug Detection", "Code Explanation", "Documentation"],
responseTime: "Instant",
pricing: "$29/month",
useCases: ["Software Development", "Web Development", "Mobile Apps", "Enterprise Applications"]
},
{
id: 2,
title: "Data Analyst",
description: "Specialized AI for data analytics, visualization, and insights extraction",
capabilities: ["Data Cleaning", "Statistical Analysis", "Data Visualization", "Predictive Modeling"],
responseTime: "Within minutes",
pricing: "$49/month",
useCases: ["Business Intelligence", "Market Research", "Financial Analysis", "Customer Insights"]
},
{
id: 3,
title: "Content Creator",
description: "AI agent for generating and optimizing various types of content",
capabilities: ["Blog Writing", "Social Media Content", "Email Marketing", "SEO Optimization"],
responseTime: "Instant to minutes",
pricing: "$39/month",
useCases: ["Marketing Teams", "Content Publishers", "Social Media Managers", "Small Businesses"]
},
{
id: 4,
title: "UI/UX Design Assistant",
description: "AI that helps create design mockups, wireframes, and UI elements",
capabilities: ["Wireframing", "UI Design", "Visual Elements", "Design Feedback"],
responseTime: "Within minutes",
pricing: "$59/month",
useCases: ["Product Teams", "Web Designers", "App Developers", "Creative Agencies"]
},
{
id: 5,
title: "Research Assistant",
description: "AI agent that helps with research, information gathering, and summarization",
capabilities: ["Literature Review", "Data Collection", "Summarization", "Citation Management"],
responseTime: "Within minutes",
pricing: "$45/month",
useCases: ["Academic Research", "Market Research", "Competitive Analysis", "Legal Research"]
}
];
export default function HireAIAgent() {
const [selectedAgent, setSelectedAgent] = useState<AIAgent | null>(null);
const [dialogOpen, setDialogOpen] = useState(false);
const [searchTerm, setSearchTerm] = useState("");
// Filter AI agents based on search term
const filteredAgents = aiAgentTypes.filter(agent =>
agent.title.toLowerCase().includes(searchTerm.toLowerCase()) ||
agent.description.toLowerCase().includes(searchTerm.toLowerCase()) ||
agent.capabilities.some(cap => cap.toLowerCase().includes(searchTerm.toLowerCase())) ||
agent.useCases.some(useCase => useCase.toLowerCase().includes(searchTerm.toLowerCase()))
);
return (
<div className="container mx-auto px-4 py-12">
<h1 className="text-center text-3xl sm:text-4xl font-bold text-[#6d9e37] mb-3 sm:mb-4">Hire an AI Agent</h1>
<p className="text-center text-lg sm:text-xl max-w-3xl mx-auto text-neutral-300 mb-10">
Leverage our AI agents to automate tasks, generate content, analyze data, and more
</p>
{/* Hero Banner */}
<div className="bg-neutral-800 text-white rounded-lg p-8 mb-12">
<div className="flex flex-col md:flex-row md:items-center">
<div className="md:w-2/3 mb-6 md:mb-0 md:pr-6">
<h2 className="text-2xl md:text-3xl font-bold mb-4 text-[#6d9e37]">Why Choose AI Agents?</h2>
<p className="mb-4">
Our AI agents work 24/7, provide instant results, and cost a fraction of human labor. They excel at repetitive tasks, data processing, and creative content generation.
</p>
<div className="grid grid-cols-2 gap-4 mt-6">
<div className="flex items-start">
<div className="bg-white bg-opacity-20 p-2 rounded mr-3">
<svg xmlns="http://www.w3.org/2000/svg" className="h-5 w-5" viewBox="0 0 20 20" fill="#6d9e37">
<path fillRule="evenodd" d="M11.3 1.046A1 1 0 0112 2v5h4a1 1 0 01.82 1.573l-7 10A1 1 0 018 18v-5H4a1 1 0 01-.82-1.573l7-10a1 1 0 011.12-.38z" clipRule="evenodd" />
</svg>
</div>
<div>
<h3 className="font-semibold text-sm">Instant Results</h3>
<p className="text-xs text-zinc-300">No waiting for human availability</p>
</div>
</div>
<div className="flex items-start">
<div className="bg-white bg-opacity-20 p-2 rounded mr-3">
<svg xmlns="http://www.w3.org/2000/svg" className="h-5 w-5" viewBox="0 0 20 20" fill="#6d9e37">
<path fillRule="evenodd" d="M18 10a8 8 0 11-16 0 8 8 0 0116 0zm-7-4a1 1 0 11-2 0 1 1 0 012 0zM9 9a1 1 0 000 2v3a1 1 0 001 1h1a1 1 0 100-2v-3a1 1 0 00-1-1H9z" clipRule="evenodd" />
</svg>
</div>
<div>
<h3 className="font-semibold text-sm">Cost Effective</h3>
<p className="text-xs text-zinc-300">Fraction of human labor costs</p>
</div>
</div>
</div>
</div>
<div className="md:w-1/3">
<div className="bg-white bg-opacity-10 p-6 rounded-lg text-center">
<h3 className="font-bold mb-2">Start with an AI Agent Today</h3>
<p className="text-sm mb-4 text-zinc-300">
All plans come with a 7-day free trial
</p>
<Button className="w-full">View All Agents</Button>
</div>
</div>
</div>
</div>
{/* Search */}
<div className="mb-8">
<div className="max-w-md mx-auto">
<Input
type="text"
placeholder="Search AI agents by capability or use case..."
value={searchTerm}
onChange={(e) => setSearchTerm(e.target.value)}
className="mb-4"
/>
</div>
</div>
{/* AI Agent Grid */}
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6">
{filteredAgents.map((agent) => (
<Card key={agent.id} className="hover:shadow-md transition">
<CardHeader>
<CardTitle>{agent.title}</CardTitle>
<CardDescription>{agent.description}</CardDescription>
</CardHeader>
<CardContent>
<div className="mb-4">
<h4 className="text-sm font-semibold mb-2">Key Capabilities:</h4>
<div className="flex flex-wrap gap-2">
{agent.capabilities.map((capability, index) => (
<span
key={index}
className="inline-block bg-zinc-100 text-zinc-800 text-xs px-2 py-1 rounded"
>
{capability}
</span>
))}
</div>
</div>
<div className="space-y-2 text-sm">
<div className="flex justify-between">
<span className="text-zinc-500">Response Time:</span>
<span className="font-medium text-green-600">{agent.responseTime}</span>
</div>
<div className="flex justify-between">
<span className="text-zinc-500">Pricing:</span>
<span className="font-medium">{agent.pricing}</span>
</div>
</div>
</CardContent>
<CardFooter>
<Button className="w-full" onClick={() => {setSelectedAgent(agent); setDialogOpen(true);}}>View Details</Button>
</CardFooter>
</Card>
))}
</div>
{/* AI Agent Details Dialog */}
<Dialog open={dialogOpen} onOpenChange={setDialogOpen}>
<DialogContent className="sm:max-w-md">
{selectedAgent && (
<>
<DialogHeader>
<DialogTitle>{selectedAgent.title}</DialogTitle>
<DialogDescription>
{selectedAgent.description}
</DialogDescription>
</DialogHeader>
<div className="space-y-4 py-4">
<div>
<h4 className="text-sm font-semibold mb-1">Capabilities:</h4>
<div className="flex flex-wrap gap-2">
{selectedAgent.capabilities.map((capability, index) => (
<span
key={index}
className="inline-block bg-zinc-100 text-zinc-800 text-xs px-2 py-1 rounded"
>
{capability}
</span>
))}
</div>
</div>
<div>
<h4 className="text-sm font-semibold mb-1">Common Use Cases:</h4>
<ul className="list-disc pl-5 text-zinc-600 text-sm">
{selectedAgent.useCases.map((useCase, index) => (
<li key={index}>{useCase}</li>
))}
</ul>
</div>
<div className="grid grid-cols-2 gap-4">
<div>
<h4 className="text-sm font-semibold mb-1">Response Time:</h4>
<p className="font-medium text-green-600">{selectedAgent.responseTime}</p>
</div>
<div>
<h4 className="text-sm font-semibold mb-1">Pricing:</h4>
<p className="font-medium">{selectedAgent.pricing}</p>
</div>
</div>
<div className="border-t border-gray-200 pt-4">
<h4 className="text-sm font-semibold mb-1">Subscription Includes:</h4>
<ul className="text-sm text-zinc-600 space-y-1">
<li className="flex items-center">
<svg xmlns="http://www.w3.org/2000/svg" className="h-4 w-4 text-green-500 mr-2" viewBox="0 0 20 20" fill="#6d9e37">
<path fillRule="evenodd" d="M16.707 5.293a1 1 0 010 1.414l-8 8a1 1 0 01-1.414 0l-4-4a1 1 0 011.414-1.414L8 12.586l7.293-7.293a1 1 0 011.414 0z" clipRule="evenodd" />
</svg>
Unlimited requests
</li>
<li className="flex items-center">
<svg xmlns="http://www.w3.org/2000/svg" className="h-4 w-4 text-green-500 mr-2" viewBox="0 0 20 20" fill="#6d9e37">
<path fillRule="evenodd" d="M16.707 5.293a1 1 0 010 1.414l-8 8a1 1 0 01-1.414 0l-4-4a1 1 0 011.414-1.414L8 12.586l7.293-7.293a1 1 0 011.414 0z" clipRule="evenodd" />
</svg>
24/7 availability
</li>
<li className="flex items-center">
<svg xmlns="http://www.w3.org/2000/svg" className="h-4 w-4 text-green-500 mr-2" viewBox="0 0 20 20" fill="#6d9e37">
<path fillRule="evenodd" d="M16.707 5.293a1 1 0 010 1.414l-8 8a1 1 0 01-1.414 0l-4-4a1 1 0 011.414-1.414L8 12.586l7.293-7.293a1 1 0 011.414 0z" clipRule="evenodd" />
</svg>
API access
</li>
<li className="flex items-center">
<svg xmlns="http://www.w3.org/2000/svg" className="h-4 w-4 text-green-500 mr-2" viewBox="0 0 20 20" fill="#6d9e37">
<path fillRule="evenodd" d="M16.707 5.293a1 1 0 010 1.414l-8 8a1 1 0 01-1.414 0l-4-4a1 1 0 011.414-1.414L8 12.586l7.293-7.293a1 1 0 011.414 0z" clipRule="evenodd" />
</svg>
Export capabilities
</li>
</ul>
</div>
</div>
<DialogFooter>
<Button type="button" variant="outline" onClick={() => setDialogOpen(false)}>
Cancel
</Button>
<Button type="button">
Subscribe Now
</Button>
</DialogFooter>
</>
)}
</DialogContent>
</Dialog>
{/* Comparison Section */}
<div className="mt-16">
<h2 className="text-2xl font-bold mb-8 text-center">AI Agents vs. Human Developers</h2>
<div className="overflow-x-auto">
<table className="w-full border-collapse">
<thead>
<tr className="bg-neutral-800 text-white">
<th className="p-4 text-center border-[1px] border-[#6d9e37] w-1/3">Feature</th>
<th className="p-4 text-center border-[1px] border-[#6d9e37] w-1/3">AI Agents</th>
<th className="p-4 text-center border-[1px] border-[#6d9e37] w-1/3">Human Developers</th>
</tr>
</thead>
<tbody>
<tr>
<td className="p-4 border-[1px] border-[#6d9e37] font-medium">Availability</td>
<td className="p-4 border-[1px] border-[#6d9e37]">24/7, instant access</td>
<td className="p-4 border-[1px] border-[#6d9e37]">Limited by working hours and availability</td>
</tr>
<tr className="bg-neutral-800 text-white">
<td className="p-4 border-[1px] border-[#6d9e37] font-medium">Cost</td>
<td className="p-4 border-[1px] border-[#6d9e37]">Low monthly subscription</td>
<td className="p-4 border-[1px] border-[#6d9e37]">Higher hourly or project-based rates</td>
</tr>
<tr>
<td className="p-4 border-[1px] border-[#6d9e37] font-medium">Task Complexity</td>
<td className="p-4 border-[1px] border-[#6d9e37]">Best for repetitive, well-defined tasks</td>
<td className="p-4 border-[1px] border-[#6d9e37]">Better for complex, novel problems</td>
</tr>
<tr className="bg-neutral-800 text-white">
<td className="p-4 border-[1px] border-[#6d9e37] font-medium">Creativity</td>
<td className="p-4 border-[1px] border-[#6d9e37]">Limited to patterns in training data</td>
<td className="p-4 border-[1px] border-[#6d9e37]">Higher level of creativity and innovation</td>
</tr>
<tr>
<td className="p-4 border-[1px] border-[#6d9e37] font-medium">Scalability</td>
<td className="p-4 border-[1px] border-[#6d9e37]">Highly scalable at no extra cost</td>
<td className="p-4 border-[1px] border-[#6d9e37]">Requires additional hiring and onboarding</td>
</tr>
</tbody>
</table>
</div>
</div>
{/* CTA Section */}
<div className="mt-16 bg-neutral-800 p-8 rounded-lg text-center">
<h2 className="text-2xl font-bold mb-4 text-[#6d9e37]">Not sure which AI agent is right for you?</h2>
<p className="text-neutral-300 max-w-2xl mx-auto mb-8">
Our AI solution experts can help you determine which AI agent best fits your specific needs and use cases.
</p>
<Button size="lg">
Schedule a Consultation
</Button>
</div>
</div>
);
}

View File

@@ -0,0 +1,263 @@
"use client";
import { useState } from "react";
import { Card, CardContent, CardDescription, CardFooter, CardHeader, CardTitle } from "./ui/card";
import { Button } from "./ui/button";
import { Input } from "./ui/input";
import { Dialog, DialogContent, DialogDescription, DialogFooter, DialogHeader, DialogTitle } from "./ui/dialog";
// Define the Developer type
interface Developer {
id: number;
title: string;
description: string;
skills: string[];
availability: "High" | "Medium" | "Low";
minBudget: string;
contractFee: string;
}
// Developer types data
const developerTypes: Developer[] = [
{
id: 1,
title: "Web Developer",
description: "Frontend, backend or full-stack developers specialized in web technologies",
skills: ["React", "Angular", "Vue", "Node.js", "Django", "Ruby on Rails", "Laravel", "Express", "Flask", "PHP", "JavaScript", "TypeScript", "HTML", "CSS", "SASS", "LESS", "Bootstrap", "Tailwind CSS", "Material-UI", "Ant Design", "REST APIs", "GraphQL", "WebSockets", "OAuth", "JWT", "WebRTC", "PWA", "SEO", "Accessibility", "Performance", "Security"],
availability: "High",
minBudget: "Budget-friendly",
contractFee: "Nominal"
},
{
id: 2,
title: "App Developer",
description: "iOS and Android native or cross-platform mobile app developers",
skills: ["Swift", "Kotlin", "React Native", "Flutter", "Xamarin", "Ionic", "PhoneGap", "Cordova", "NativeScript", "Appcelerator", "Java", "Objective-C", "Firebase", "Realm", "Core Data", "SQLite", "Push Notifications", "In-App Purchases", "ARKit", "Core ML", "Android Jetpack", "Material Design", "Google Play Services", "App Store Connect", "Google Play Console"],
availability: "Medium",
minBudget: "Limited",
contractFee: "Modest"
},
{
id: 3,
title: "C/C++ Developer",
description: "Systems programmers and embedded systems specialists",
skills: ["C", "C++", "Embedded Systems", "Linux Kernel", "Real-time Systems", "RTOS", "Device Drivers", "Firmware", "Microcontrollers", "ARM", "AVR", "PIC", "Raspberry Pi", "Arduino", "BeagleBone", "Zephyr", "FreeRTOS", "VxWorks", "QNX", "Bare Metal"],
availability: "Low",
minBudget: "Increased",
contractFee: "Considerable"
},
{
id: 4,
title: "Go Developer",
description: "Backend and systems engineers specializing in Go language",
skills: ["Go", "Microservices", "Docker", "Kubernetes", "API Development", "gRPC", "Protobuf", "WebSockets", "WebAssembly", "Concurrency", "Performance Tuning", "Module Building", "Error Handling", "Testing", "Security"],
availability: "Medium",
minBudget: "Boosted",
contractFee: "Elevated"
},
{
id: 5,
title: "Machine Learning Engineer",
description: "AI and ML specialists for implementing intelligent solutions",
skills: ["Python", "TensorFlow", "PyTorch", "Data Science", "MLOps", "NLP", "Computer Vision", "Reinforcement Learning", "Time Series Analysis", "Anomaly Detection", "Recommendation Systems", "Predictive Modeling", "Deep Learning", "Fraud Detection", "Sentiment Analysis", "Speech Recognition", "Image Processing", "Object Detection", "Generative Models", "AutoML", "Image Recognition", "Text Classification", "Chatbots", "AI Ethics"],
availability: "Low",
minBudget: "Premium",
contractFee: "Lavish"
},
{
id: 6,
title: "DevOps Engineer",
description: "Specialists in CI/CD, cloud infrastructure, and deployment automation",
skills: ["AWS", "Azure", "GCP", "Terraform", "Ansible", "Jenkins", "Docker", "Kubernetes", "GitOps", "Monitoring", "Logging", "Security", "Compliance", "Scalability", "Reliability", "Resilience", "Performance", "Cost Optimization", "Disaster Recovery", "Incident Response", "SRE"],
availability: "Medium",
minBudget: "Pricier",
contractFee: "Substantial"
}
];
export default function HireHumanDeveloper() {
const [selectedType, setSelectedType] = useState<Developer | null>(null);
const [searchTerm, setSearchTerm] = useState("");
const [dialogOpen, setDialogOpen] = useState(false);
// Filter developers based on search term
const filteredDevelopers = developerTypes.filter(dev =>
dev.title.toLowerCase().includes(searchTerm.toLowerCase()) ||
dev.description.toLowerCase().includes(searchTerm.toLowerCase()) ||
dev.skills.some(skill => skill.toLowerCase().includes(searchTerm.toLowerCase()))
);
return (
<div className="container mx-auto px-4 py-12">
<h1 className="text-center text-3xl sm:text-4xl font-bold text-[#6d9e37] mb-3 sm:mb-4">Hire a Human Developer</h1>
<p className="text-center text-lg sm:text-xl max-w-3xl mx-auto text-neutral-300 mb-10">
Connect with skilled developers across various technologies and specialties
</p>
{/* Search and Filter */}
<div className="mb-8">
<div className="max-w-md mx-auto">
<Input
type="text"
placeholder="Search by technology, skill, or type..."
value={searchTerm}
onChange={(e) => setSearchTerm(e.target.value)}
className="mb-4"
/>
</div>
</div>
{/* Developer Types Grid */}
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6">
{filteredDevelopers.map((developer) => (
<Card key={developer.id} className="hover:shadow-md transition">
<CardHeader>
<CardTitle>{developer.title}</CardTitle>
<CardDescription>{developer.description}</CardDescription>
</CardHeader>
<CardContent>
<div className="mb-4">
<h4 className="text-sm font-semibold mb-2 text-neutral-300">Key Skills:</h4>
<div className="flex flex-wrap gap-2">
{developer.skills.map((skill, index) => (
<span
key={index}
className="inline-block bg-neutral-300 text-zinc-800 text-xs px-2 py-1 rounded"
>
{skill}
</span>
))}
</div>
</div>
<div className="space-y-2 text-sm">
<div className="flex justify-between">
<span className="text-zinc-500">Availability:</span>
<span className={`font-medium ${
developer.availability === "High" ? "text-green-600" :
developer.availability === "Medium" ? "text-amber-600" : "text-red-600"
}`}>
{developer.availability}
</span>
</div>
<div className="flex justify-between">
<span className="text-zinc-500">Starting at:</span>
<span className="font-medium">{developer.minBudget}</span>
</div>
<div className="flex justify-between">
<span className="text-zinc-500">Contract Fee:</span>
<span className="font-medium">{developer.contractFee}</span>
</div>
</div>
</CardContent>
<CardFooter>
<Button
className="w-full"
onClick={() => {
setSelectedType(developer);
setDialogOpen(true);
}}
>
Hire Developer
</Button>
</CardFooter>
</Card>
))}
</div>
{/* Developer Details Dialog */}
<Dialog open={dialogOpen} onOpenChange={setDialogOpen}>
<DialogContent className="sm:max-w-md">
{selectedType && (
<>
<DialogHeader>
<DialogTitle>{selectedType.title} Details</DialogTitle>
<DialogDescription>
Review the details and proceed to hire
</DialogDescription>
</DialogHeader>
<div className="space-y-4 py-4">
<div>
<h4 className="text-sm font-semibold mb-1 text-neutral-950">Overview:</h4>
<p className="text-neutral-400">{selectedType.description}</p>
</div>
<div>
<h4 className="text-sm font-semibold mb-1 text-neutral-950">Specialized Skills:</h4>
<div className="flex flex-wrap gap-2">
{selectedType.skills.map((skill, index) => (
<span
key={index}
className="inline-block bg-zinc-100 text-zinc-800 text-xs px-2 py-1 rounded"
>
{skill}
</span>
))}
</div>
</div>
<div className="grid grid-cols-2 gap-4">
<div>
<h4 className="text-sm font-semibold mb-1 text-neutral-950">Availability:</h4>
<p className={`font-medium ${
selectedType.availability === "High" ? "text-green-600" :
selectedType.availability === "Medium" ? "text-amber-600" : "text-red-600"
}`}>
{selectedType.availability}
</p>
</div>
<div>
<h4 className="text-sm font-semibold mb-1 text-neutral-950">Minimum Budget:</h4>
<p className="font-medium text-neutral-950">{selectedType.minBudget}</p>
</div>
<div>
<h4 className="text-sm font-semibold mb-1 text-neutral-950">Contract Fee:</h4>
<p className="font-medium text-neutral-950">{selectedType.contractFee}</p>
</div>
<div>
<h4 className="text-sm font-semibold mb-1 text-neutral-950">Usual Turnaround:</h4>
<p className="font-medium text-neutral-950">1-3 weeks</p>
</div>
</div>
</div>
<DialogFooter>
<Button type="button" variant="outline" onClick={() => setDialogOpen(false)}>
Cancel
</Button>
<Button type="button">
Proceed to Hire
</Button>
</DialogFooter>
</>
)}
</DialogContent>
</Dialog>
{/* Additional Information */}
<div className="mt-16 bg-neutral-800 p-8 rounded-lg">
<h2 className="text-2xl font-bold mb-4 text-[#6d9e37]">Why Hire Our Human Developers?</h2>
<div className="grid grid-cols-1 md:grid-cols-3 gap-6">
<div>
<h3 className="text-lg font-semibold mb-2 text-[#6d9e37]">Vetted Experts</h3>
<p className="text-[#fff]">
All our developers go through a rigorous vetting process to ensure top-quality talent.
</p>
</div>
<div>
<h3 className="text-lg font-semibold mb-2 text-[#6d9e37]">Flexible Engagement</h3>
<p className="text-[#fff]">
Hire on hourly, project-based, or long-term contracts based on your needs.
</p>
</div>
<div>
<h3 className="text-lg font-semibold mb-2 text-[#6d9e37]">Satisfaction Guarantee</h3>
<p className="text-[#fff]">
Not satisfied with the talent? We'll match you with another developer at no extra cost.
</p>
</div>
</div>
</div>
</div>
);
}

280
src/components/Login.tsx Normal file
View File

@@ -0,0 +1,280 @@
import { useState } from 'react';
import type { FormEvent } from 'react';
import PocketBase from 'pocketbase';
import { Input } from "./ui/input";
import { Button } from "./ui/button";
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "./ui/card";
import { Eye, EyeOff, Loader2 } from "lucide-react";
import { Separator } from "./ui/separator";
import { Label } from "./ui/label";
interface AuthStatus {
message: string;
isError: boolean;
}
interface UserRecord {
id: string;
email: string;
name?: string;
avatar?: string;
[key: string]: any;
}
interface AuthResponse {
token: string;
record: UserRecord;
}
const LoginPage = () => {
const [email, setEmail] = useState('suvodip@siliconpin.com');
const [password, setPassword] = useState('Simple2pass');
const [passwordVisible, setPasswordVisible] = useState(false);
const [status, setStatus] = useState<AuthStatus>({ message: '', isError: false });
const [isLoading, setIsLoading] = useState(false);
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);
setStatus({ message: '', isError: false });
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,
record: {
query: 'new',
id: authData.record.id,
email: authData.record.email,
name: authData.record.name || '',
avatar: authData.record.avatar || ''
}
};
await syncSessionWithBackend(authResponse, avatarUrl);
window.location.href = '/profile';
} catch (error) {
console.error("Login failed:", error);
setStatus({
message: "Login failed. Please check your credentials.",
isError: true
});
} finally {
setIsLoading(false);
}
};
const loginWithOAuth2 = async (provider: 'google' | 'facebook' | 'github') => {
try {
setIsLoading(true);
setStatus({ message: '', isError: false });
const authData = await pb.collection('users').authWithOAuth2({ provider });
if (!authData?.record) {
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: {
query: 'new',
id: authData.record.id,
email: authData.record.email || '',
name: authData.record.name || '',
avatar: authData.record.avatar || ''
}
};
await syncSessionWithBackend(authResponse, avatarUrl);
window.location.href = '/profile';
} catch (error) {
console.error(`${provider} Login failed:`, error);
setStatus({
message: `${provider} login failed. Please try again.`,
isError: true
});
} finally {
setIsLoading(false);
}
};
const syncSessionWithBackend = async (authData: AuthResponse, avatarUrl: string) => {
try {
const response = await fetch('http://localhost:2058/host-api/v1/users/session/', {
method: 'POST',
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: avatarUrl,
isAuthenticated: true,
id: authData.record.id
})
});
if (!response.ok) {
throw new Error('Failed to sync session');
}
const data = await response.json();
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">
<Card className="w-full max-w-md shadow-lg rounded-xl overflow-hidden">
<CardHeader className="text-center space-y-1">
<CardTitle className="text-2xl font-bold">Welcome Back</CardTitle>
<CardDescription className="">
Sign in to access your account
</CardDescription>
</CardHeader>
<CardContent className="space-y-6">
{status.message && (
<div className={`p-3 rounded-md text-sm ${status.isError ? 'bg-red-50 text-red-600' : 'bg-green-50 text-green-600'}`}>
{status.message}
</div>
)}
<form onSubmit={handleSubmit} className="space-y-4">
<div className="space-y-2">
<Label htmlFor="email" className="">
Email Address
</Label>
<Input
id="email"
type="email"
value={email}
onChange={(e) => setEmail(e.target.value)}
placeholder="you@example.com"
required
className="focus:ring-2 focus:ring-blue-500"
/>
</div>
<div className="space-y-2">
<div className="flex justify-between items-center">
<Label htmlFor="password" className="">
Password
</Label>
<button
type="button"
onClick={() => setPasswordVisible(!passwordVisible)}
className="text-sm text-[#6d9e37]"
>
</button>
</div>
<div className="relative">
<Input
id="password"
type={passwordVisible ? "text" : "password"}
value={password}
onChange={(e) => setPassword(e.target.value)}
placeholder="••••••••"
required
className="focus:ring-2 focus:ring-blue-500 pr-10"
/>
<button
type="button"
onClick={() => setPasswordVisible(!passwordVisible)}
className="absolute right-3 top-1/2 transform -translate-y-1/2 text-gray-400 hover:text-gray-600"
>
{passwordVisible ? <EyeOff className="h-5 w-5" /> : <Eye className="h-5 w-5" />}
</button>
</div>
</div>
<Button
type="submit"
disabled={isLoading}
className="w-full"
>
{isLoading ? (
<>
<Loader2 className="mr-2 h-4 w-4 animate-spin" />
Signing in...
</>
) : (
'Sign In'
)}
</Button>
</form>
<div className="relative">
<div className="absolute inset-0 flex items-center">
<Separator className="w-full" />
</div>
<div className="relative flex justify-center text-xs uppercase">
<span className="bg-background px-2 text-muted-foreground">
Or continue with
</span>
</div>
</div>
<div className="grid grid-cols-3 gap-3">
<Button
variant="outline"
onClick={() => loginWithOAuth2('google')}
disabled={isLoading}
className="flex items-center justify-center gap-2"
>
<img src="/assets/google.svg" alt="Google" className="h-6 w-6" />
</Button>
<Button
variant="outline"
onClick={() => loginWithOAuth2('facebook')}
disabled={isLoading}
className="flex items-center justify-center gap-2"
>
<img src="/assets/facebook.svg" alt="Facebook" className="h-6 w-6" />
</Button>
<Button
variant="outline"
onClick={() => loginWithOAuth2('github')}
disabled={isLoading}
className="flex items-center justify-center gap-2"
>
<img src="/assets/github.svg" alt="GitHub" className="h-6 w-6" />
</Button>
</div>
</CardContent>
<div className="px-6 pb-6 text-center text-sm text-gray-500">
Don't have an account?{' '}
<a href="/sign-up" className="font-medium text-[#6d9e37] hover:underline">
Sign up
</a>
</div>
</Card>
</div>
);
};
export default LoginPage;

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

@@ -36,7 +36,7 @@ export function ServiceCard({ title, description, imageUrl, features, learnMoreU
>
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M5 13l4 4L19 7" />
</svg>
<span>{feature}</span>
<span className='text-zinc-600'>{feature}</span>
</li>
))}
</ul>

365
src/components/Ticket.tsx Normal file
View File

@@ -0,0 +1,365 @@
import React, { useState, useEffect } from "react";
import { Button } from "./ui/button";
import { Card, CardContent, CardDescription, CardFooter, CardHeader, CardTitle } from "./ui/card";
import Table from "./ui/table";
import { Dialog, DialogContent, DialogHeader, DialogTitle, DialogDescription } from "./ui/dialog";
import { Input } from "./ui/input";
import { Textarea } from "./ui/textarea";
import { Label } from "./ui/label";
import { Select, SelectTrigger, SelectValue, SelectContent, SelectItem } from "./ui/select";
import { CustomTabs } from "./ui/CustomTabs";
import {localizeTime} from "../lib/localizeTime";
interface Ticket {
id: number;
title: string;
description: string;
status: 'open' | 'in-progress' | 'resolved' | 'closed'; // Add 'closed' here
created_at: string;
updated_at: string;
created_by: number;
}
interface Message {
id: number;
ticket_id: number;
user_id: number;
message: string;
created_at: string;
user_type: string;
}
const API_URL = 'http://localhost:2058/host-api/app/v1/ticket/index.php';
function Ticketing() {
const [tickets, setTickets] = useState<Ticket[]>([]);
const [messages, setMessages] = useState<Message[]>([]);
const [selectedTicket, setSelectedTicket] = useState<(Ticket & { status: 'open' | 'in-progress' | 'resolved' | 'closed' }) | null>(null);
const [activeTab, setActiveTab] = useState<'open' | 'in-progress' | 'resolved' | 'closed'>('open');
const [isDialogOpen, setIsDialogOpen] = useState(false);
const [isMessageDialogOpen, setIsMessageDialogOpen] = useState(false);
const [newMessage, setNewMessage] = useState('');
const [formData, setFormData] = useState({title: '', description: '', status: 'open' as const, });
// Fetch tickets
useEffect(() => {fetchTickets(); }, [activeTab]);
// Fetch messages when ticket is selected
useEffect(() => {if (selectedTicket) {fetchMessages(selectedTicket.id);}}, [selectedTicket]);
const fetchTickets = async () => {
try {
const response = await fetch(`${API_URL}?action=get_tickets&status=${activeTab}`);
const data = await response.json();
if (data.success) {
setTickets(data.tickets);
}
} catch (error) {
console.error('Error fetching tickets:', error);
}
};
const fetchMessages = async (ticketId: number) => {
try {
const response = await fetch(`${API_URL}?action=get_messages&ticket_id=${ticketId}`);
const data = await response.json();
console.log('Messages response:', data); // Debug log
if (data.success) {
setMessages(data.messages || []);
}
} catch (error) {
console.error('Error fetching messages:', error);
}
};
const handleInputChange = (e: React.ChangeEvent<HTMLInputElement | HTMLTextAreaElement>) => {
const { name, value } = e.target;
setFormData(prev => ({ ...prev, [name]: value }));
};
const handleSubmit = async (e: React.FormEvent) => {
e.preventDefault();
try {
const response = await fetch(API_URL, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
action: 'create_ticket',
...formData,
}),
});
const data = await response.json();
if (data.success) {
fetchTickets(); // Refresh the list
setIsDialogOpen(false);
setFormData({ title: '', description: '', status: 'open' });
}
} catch (error) {
console.error('Error creating ticket:', error);
}
};
const handleStatusChange = async (ticketId: number, newStatus: 'open' | 'in-progress' | 'resolved' | 'closed') => {
try {
const response = await fetch(API_URL, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
action: 'update_ticket_status',
ticket_id: ticketId,
status: newStatus,
}),
});
const data = await response.json();
if (data.success) {
fetchTickets(); // Refresh the list
if (selectedTicket) {
setSelectedTicket({ ...selectedTicket, status: newStatus });
}
}
} catch (error) {
console.error('Error updating ticket status:', error);
}
};
const handleSendMessage = async () => {
if (!selectedTicket || !newMessage.trim()) return;
try {
const response = await fetch(API_URL, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
action: 'add_message',
ticket_id: selectedTicket.id,
message: newMessage,
user_type: 'user'
}),
});
const data = await response.json();
if (data.success) {
fetchMessages(selectedTicket.id); // Refresh messages
setNewMessage('');
}
} catch (error) {
console.error('Error sending message:', error);
}
};
const handleDeleteTicket = async (ticketId: number) => {
try {
const response = await fetch(API_URL, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
action: 'delete_ticket',
ticket_id: ticketId,
}),
});
const data = await response.json();
if (data.success) {
fetchTickets(); // Refresh the list
if (selectedTicket?.id === ticketId) {
setIsMessageDialogOpen(false);
}
}
} catch (error) {
console.error('Error deleting ticket:', error);
}
};
// Table configuration
const tableHeaders = ['ID', 'Title', 'Description', 'Status', 'Created At', 'Actions'];
const tableData = tickets.map(ticket => ({
id: ticket.id,
title: ticket.title,
description: ticket.description,
status: ticket.status,
created_at: localizeTime(ticket.created_at),
actions: (
<div className="flex space-x-2">
<Button size="sm" onClick={() => {setSelectedTicket(ticket); setIsMessageDialogOpen(true); }}>View/Reply</Button>
<Button size="sm" onClick={() => handleDeleteTicket(ticket.id)} className="bg-red-500 hover:bg-red-600">Delete</Button>
</div>
)
}));
const statusColors = {open: 'bg-green-500', 'in-progress': 'bg-yellow-500', resolved: 'bg-blue-500', closed: 'bg-red-500', };
return (
<div className="container mx-auto p-4">
<div className="flex justify-between items-center mb-6">
<h1 className="text-2xl font-bold">Ticket / Report</h1>
<Button size="sm" onClick={() => setIsDialogOpen(true)}>Create Ticket</Button>
</div>
<CustomTabs
tabs={[
{ label: 'Open', value: 'open', content: null },
{ label: 'In Progress', value: 'in-progress', content: null },
{ label: 'Resolved', value: 'resolved', content: null },
{ label: 'Closed', value: 'closed', content: null },
]}
defaultValue={activeTab}
onValueChange={(value) => setActiveTab(value as any)}
/>
<table className="w-full border-collapse">
<thead>
<tr className="bg-neutral-800 text-white">
{
tableHeaders.map((thead, index) => (
<th key={index} className={`p-2 text-center border-[1px] border-[#6d9e37]`}>{thead}</th>
))
}
</tr>
</thead>
{
tableData.length > 0 ? (
<tbody className="[&>tr:nth-child(even)]:bg-[#262626]">
{
tableData.map((data, index) => (
<tr key={index}>
<td className="px-2 border-[1px] border-[#6d9e37] font-medium">{data.id}</td>
<td className="px-2 border-[1px] border-[#6d9e37] font-medium">{data.title}</td>
<td className="px-2 border-[1px] border-[#6d9e37] font-medium">{data.description}</td>
<td className="px-2 border-[1px] border-[#6d9e37] font-medium text-center">
<span className={`text-white px-2 py-1 rounded ${statusColors[data.status] || ''}`}>{data.status}</span>
</td>
<td className="px-2 border-[1px] border-[#6d9e37] font-medium text-center">{data.created_at}</td>
<td className="px-2 border-[1px] border-[#6d9e37] font-medium flex items-center justify-center p-1">{data.actions}</td>
</tr>
))
}
</tbody>
) : (
<tbody>
<tr>
<th colSpan={6} className="p-4 border-[1px] border-[#6d9e37] font-medium">No {activeTab.charAt(0).toUpperCase() + activeTab.slice(1)} Ticket Found</th>
</tr>
</tbody>
)
}
</table>
{/* <Table headers={tableHeaders} data={tableData} striped hover /> */}
{/* Create Ticket Dialog */}
<Dialog open={isDialogOpen} onOpenChange={setIsDialogOpen}>
<DialogContent>
<DialogHeader>
<DialogTitle>Create New Ticket</DialogTitle>
</DialogHeader>
<form onSubmit={handleSubmit} className="space-y-4">
<div>
<Label htmlFor="title">Title</Label>
<Input id="title" name="title" value={formData.title} onChange={handleInputChange} required className="w-full bg-white rounded-md outline-none border border-[#6d9e37] p-2" placeholder="Write ticket title" />
</div>
<div>
<Label htmlFor="description">Description</Label>
<textarea id="description" name="description" value={formData.description} onChange={handleInputChange} rows={4} className="w-full rounded-md outline-none border border-[#6d9e37] p-2" required placeholder="Write description here..."></textarea>
</div>
<div className="flex justify-end space-x-2">
<Button variant="outline" onClick={() => setIsDialogOpen(false)}>Cancel</Button>
<Button type="submit">Create</Button>
</div>
</form>
</DialogContent>
</Dialog>
{/* Ticket Messages Dialog */}
<Dialog open={isMessageDialogOpen} onOpenChange={setIsMessageDialogOpen}>
<DialogContent className="max-w-2xl">
<DialogHeader>
<DialogTitle>Ticket #{selectedTicket?.id}: {selectedTicket?.title}</DialogTitle>
</DialogHeader>
{selectedTicket && (
<div className="space-y-4">
<div className="p-4 bg-gray-50 rounded">
<p className="font-semibold text-gray-500">Ticket Details</p>
<p className="text-gray-500">{selectedTicket.description}</p>
<p className="text-sm text-gray-500 mt-2 ">
<strong>Status:</strong> <span className={`font-bold px-1 rounded-md text-white ${statusColors[selectedTicket.status] || ''}`}>{selectedTicket.status}</span>&nbsp;|&nbsp;
<strong>Created:</strong> {new Date(selectedTicket.created_at).toLocaleString()}
</p>
</div>
<div className="border-t pt-4">
<p className="font-semibold mb-2 text-[#6d9e37]">Conversation</p>
<div className="space-y-4 max-h-64 overflow-y-auto">
{
messages.length > 0 ? (
messages.map(message => (
<div key={message.id} className={`px-3 py-1 border-b rounded flex flex-col text-gray-500 ${message.user_type === 'user' ? 'justify-end items-end border-[#6d9e37]' : 'justify-start items-start border-gray-500'} `}>
<p>{message.message}</p>
<p className="text-xs text-gray-500 mt-1 inline-flex items-center gap-1">
<img src="/assets/clock.svg" alt="" />
{localizeTime(message.created_at)}</p>
</div>
))
) : (
<p className="text-gray-500">No messages yet</p>
)
}
</div>
</div>
<div className="border-t pt-4">
<Label htmlFor="newMessage" className="text-[#6d9e37]">Add Reply</Label>
<div className="flex flex-row gap-x-2">
<textarea placeholder="Write your message..." id="newMessage" rows={1} value={newMessage} onChange={(e) => setNewMessage(e.target.value)} className="w-full p-2 mb-2 border border-[#6d9e37] outline-none focus:outline-none rounded text-gray-500 resize-none"></textarea>
<Button onClick={handleSendMessage} disabled={!newMessage.trim()} className="px-6">
<img src="/assets/send.svg" alt="" />
</Button>
</div>
<div className="flex justify-between">
<div className="space-x-2">
{selectedTicket.status === 'closed' ? (
<Button
size="sm"
variant="default"
onClick={() => handleStatusChange(selectedTicket.id, 'open')}
className="bg-green-600 hover:bg-green-700"
>
Reopen
</Button>
) : (
<>
{selectedTicket.status !== 'in-progress' && (
<Button
size="sm"
variant="outline"
onClick={() => handleStatusChange(selectedTicket.id, 'in-progress')}
>
Mark as In Progress
</Button>
)}
{selectedTicket.status !== 'resolved' && (
<Button
size="sm"
variant="outline"
onClick={() => handleStatusChange(selectedTicket.id, 'resolved')}
>
Mark as Resolved
</Button>
)}
<Button
size="sm"
variant="outline"
onClick={() => handleStatusChange(selectedTicket.id, 'closed')}
>
Close
</Button>
</>
)}
</div>
</div>
</div>
</div>
)}
</DialogContent>
</Dialog>
</div>
);
}
export default Ticketing;

View File

@@ -0,0 +1,590 @@
import React, { useState, useEffect } from "react";
import { Button } from "./ui/button";
import { Input } from "./ui/input";
import { Label } from "./ui/label";
import { Textarea } from "./ui/textarea";
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "./ui/select";
import { Dialog, DialogContent, DialogDescription, DialogFooter, DialogHeader, DialogTitle } from "./ui/dialog";
import { CustomTabs } from "./ui/CustomTabs";
import Table from "./ui/table";
import { Avatar, AvatarFallback, AvatarImage } from "./ui/avatar";
import { Card, CardHeader, CardContent, CardFooter } from "./ui/card";
import { Badge } from "./ui/badge";
import { formatDistanceToNow } from "./ui/date-format";
interface Message {
id?: number;
content: string;
sender: 'user' | 'admin';
createdAt: string;
}
interface Ticket {
id: number;
title: string;
description: string;
messages: Message[];
status: 'open' | 'in-progress' | 'resolved' | 'closed';
priority: 'low' | 'medium' | 'high';
category?: string;
assignedTo?: string;
createdAt: string;
updatedAt: string;
}
export default function Ticketing() {
const [tickets, setTickets] = useState<Ticket[]>([]);
const [filteredTickets, setFilteredTickets] = useState<Ticket[]>([]);
const [loading, setLoading] = useState(true);
const [error, setError] = useState<string | null>(null);
const [isDialogOpen, setIsDialogOpen] = useState(false);
const [isDetailOpen, setIsDetailOpen] = useState(false);
const [currentTicket, setCurrentTicket] = useState<Partial<Ticket>>({
title: '',
description: '',
status: 'open',
priority: 'medium',
category: 'general'
});
const [selectedTicket, setSelectedTicket] = useState<Ticket | null>(null);
const [newMessage, setNewMessage] = useState('');
const [isEditing, setIsEditing] = useState(false);
const [activeTab, setActiveTab] = useState('open');
const [searchTerm, setSearchTerm] = useState('');
const API_URL = 'http://localhost:2058/host-api/v1/ticketing/index.php';
const fetchTickets = async () => {
try {
setLoading(true);
const response = await fetch(API_URL);
if (!response.ok) {
throw new Error('Failed to fetch tickets');
}
const data = await response.json();
// Convert admin_message to messages array for backward compatibility
const processedData = data.map((ticket: any) => ({
...ticket,
messages: [
{
content: ticket.description,
sender: 'user',
createdAt: ticket.createdAt
},
...(ticket.admin_message ? [{
content: ticket.admin_message,
sender: 'admin',
createdAt: ticket.updatedAt
}] : [])
]
}));
setTickets(processedData);
filterTickets(processedData, activeTab, searchTerm);
} catch (err) {
setError(err instanceof Error ? err.message : 'An unknown error occurred');
} finally {
setLoading(false);
}
};
const filterTickets = (ticketsToFilter: Ticket[], status: string, search: string) => {
let filtered = ticketsToFilter;
if (status !== 'all') {
filtered = filtered.filter(ticket => ticket.status === status);
}
if (search) {
const searchLower = search.toLowerCase();
filtered = filtered.filter(ticket =>
ticket.title.toLowerCase().includes(searchLower) ||
ticket.description.toLowerCase().includes(searchLower) ||
ticket.messages.some(msg => msg.content.toLowerCase().includes(searchLower))
);
}
setFilteredTickets(filtered);
};
const saveTicket = async () => {
try {
const method = isEditing ? 'PUT' : 'POST';
const url = isEditing ? `${API_URL}?id=${currentTicket.id}` : API_URL;
const response = await fetch(url, {
method,
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({
title: currentTicket.title,
description: currentTicket.description,
status: currentTicket.status,
priority: currentTicket.priority,
category: currentTicket.category
}),
});
if (!response.ok) {
const errorData = await response.json();
throw new Error(errorData.error || `Failed to ${isEditing ? 'update' : 'create'} ticket`);
}
await fetchTickets();
setIsDialogOpen(false);
resetForm();
} catch (err) {
setError(err instanceof Error ? err.message : 'An unknown error occurred');
}
};
const addMessage = async (ticketId: number) => {
try {
if (!newMessage.trim()) return;
const response = await fetch(`${API_URL}?id=${ticketId}`, {
method: 'PUT',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({
message: newMessage
}),
});
if (!response.ok) {
const errorData = await response.json();
throw new Error(errorData.error || 'Failed to add message');
}
setNewMessage('');
await fetchTickets();
} catch (err) {
setError(err instanceof Error ? err.message : 'An unknown error occurred');
}
};
const reopenTicket = async (ticketId: number) => {
try {
const response = await fetch(`${API_URL}?id=${ticketId}&action=reopen`, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
});
if (!response.ok) {
throw new Error('Failed to reopen ticket');
}
await fetchTickets();
} catch (err) {
setError(err instanceof Error ? err.message : 'An unknown error occurred');
}
};
const deleteTicket = async (id: number) => {
try {
const response = await fetch(`${API_URL}?id=${id}`, {
method: 'DELETE',
});
if (!response.ok) {
throw new Error('Failed to delete ticket');
}
await fetchTickets();
} catch (err) {
setError(err instanceof Error ? err.message : 'An unknown error occurred');
}
};
const viewTicketDetails = (ticket: Ticket) => {
setSelectedTicket(ticket);
setIsDetailOpen(true);
};
const initNewTicket = () => {
setCurrentTicket({
title: '',
description: '',
status: 'open',
priority: 'medium',
category: 'general'
});
setIsEditing(false);
setIsDialogOpen(true);
};
const initEditTicket = (ticket: Ticket) => {
setCurrentTicket({ ...ticket });
setIsEditing(true);
setIsDialogOpen(true);
};
const resetForm = () => {
setCurrentTicket({
title: '',
description: '',
status: 'open',
priority: 'medium',
category: 'general'
});
};
const handleInputChange = (e: React.ChangeEvent<HTMLInputElement | HTMLTextAreaElement>) => {
const { name, value } = e.target;
setCurrentTicket(prev => ({ ...prev, [name]: value }));
};
const handleSelectChange = (name: string, value: string) => {
setCurrentTicket(prev => ({ ...prev, [name]: value }));
};
useEffect(() => {
fetchTickets();
}, []);
useEffect(() => {
filterTickets(tickets, activeTab, searchTerm);
}, [activeTab, searchTerm, tickets]);
const renderTicketTable = () => {
if (loading) {
return <div className="text-center py-8">Loading tickets...</div>;
}
if (filteredTickets.length === 0) {
return <div className="text-center py-8">No tickets found</div>;
}
const tableHeaders = ['ID', 'Title', 'Category', 'Status', 'Priority', 'Created', 'Actions'];
const tableData = filteredTickets.map(ticket => ({
'ID': ticket.id,
'Title': (
<button
onClick={() => viewTicketDetails(ticket)}
className="text-left hover:underline"
>
{ticket.title}
</button>
),
'Category': ticket.category || 'General',
'Status': (
<Badge
variant={
ticket.status === 'open' ? 'default' :
ticket.status === 'in-progress' ? 'secondary' :
ticket.status === 'resolved' ? 'success' : 'outline'
}
>
{ticket.status}
</Badge>
),
'Priority': (
<Badge
variant={
ticket.priority === 'low' ? 'success' :
ticket.priority === 'medium' ? 'warning' : 'destructive'
}
>
{ticket.priority}
</Badge>
),
'Created': formatDistanceToNow(new Date(ticket.createdAt), { addSuffix: true }),
'Actions': (
<div className="flex gap-2">
<Button
variant="outline"
size="sm"
onClick={() => viewTicketDetails(ticket)}
className="h-8"
>
View
</Button>
<Button
variant="outline"
size="sm"
onClick={() => initEditTicket(ticket)}
className="h-8"
>
Edit
</Button>
{ticket.status === 'closed' ? (
<Button
size="sm"
onClick={() => reopenTicket(ticket.id)}
className="h-8"
>
Reopen
</Button>
) : (
<Button
size="sm"
onClick={() => deleteTicket(ticket.id)}
className="h-8"
>
Delete
</Button>
)}
</div>
)
}));
return (
<Table
headers={tableHeaders}
data={tableData}
className="border rounded-lg shadow-sm"
striped
hover
/>
);
};
const renderMessages = () => {
if (!selectedTicket) return null;
return (
<div className="space-y-4">
{selectedTicket.messages.map((message, index) => (
<div key={index} className={`flex ${message.sender === 'admin' ? 'justify-start' : 'justify-end'}`}>
<div className={`max-w-[80%] rounded-lg p-4 ${message.sender === 'admin' ? 'bg-gray-100' : 'bg-blue-100'}`}>
<div className="flex items-center gap-2 mb-1">
<Avatar className="h-6 w-6">
<AvatarFallback>{message.sender === 'admin' ? 'A' : 'U'}</AvatarFallback>
</Avatar>
<span className="font-medium">{message.sender === 'admin' ? 'Admin' : 'You'}</span>
<span className="text-xs text-gray-500">
{formatDistanceToNow(new Date(message.createdAt), { addSuffix: true })}
</span>
</div>
<p className="whitespace-pre-wrap">{message.content}</p>
</div>
</div>
))}
</div>
);
};
const tabs = [
{
label: "Open Tickets",
value: "open",
content: renderTicketTable()
},
{
label: "In Progress",
value: "in-progress",
content: renderTicketTable()
},
{
label: "Resolved",
value: "resolved",
content: renderTicketTable()
},
{
label: "Closed Tickets",
value: "closed",
content: renderTicketTable()
},
{
label: "All Tickets",
value: "all",
content: renderTicketTable()
},
];
return (
<div className="container mx-auto p-4">
<div className="flex justify-between items-center mb-6">
<h1 className="text-2xl font-bold">Ticketing System</h1>
<Button onClick={initNewTicket}>
Create New Ticket
</Button>
</div>
{error && (
<div className="bg-red-100 border border-red-400 text-red-700 px-4 py-3 rounded mb-4">
{error}
</div>
)}
<div className="mb-4 flex justify-between items-center">
<div className="w-1/3">
<Input
placeholder="Search tickets..."
value={searchTerm}
onChange={(e) => setSearchTerm(e.target.value)}
/>
</div>
</div>
<CustomTabs
tabs={tabs}
defaultValue="open"
onValueChange={(value) => setActiveTab(value)}
/>
{/* Ticket Dialog */}
<Dialog open={isDialogOpen} onOpenChange={setIsDialogOpen}>
<DialogContent>
<DialogHeader>
<DialogTitle>
{isEditing ? 'Edit Ticket' : 'Create New Ticket'}
</DialogTitle>
<DialogDescription>
{isEditing ? 'Update the ticket details' : 'Fill in the details for the new ticket'}
</DialogDescription>
</DialogHeader>
<div className="grid gap-4 py-4">
<div className="grid grid-cols-4 items-center gap-4">
<Label htmlFor="title" className="text-right">
Title
</Label>
<Input
id="title"
name="title"
value={currentTicket.title}
onChange={handleInputChange}
className="col-span-3"
/>
</div>
<div className="grid grid-cols-4 items-center gap-4">
<Label htmlFor="description" className="text-right">
Description
</Label>
<Textarea
id="description"
name="description"
value={currentTicket.description}
onChange={handleInputChange}
className="col-span-3"
rows={5}
/>
</div>
<div className="grid grid-cols-4 items-center gap-4">
<Label htmlFor="category" className="text-right">
Category
</Label>
<Select
value={currentTicket.category}
onValueChange={(value) => handleSelectChange('category', value)}
>
<SelectTrigger className="col-span-3">
<SelectValue placeholder="Select category" />
</SelectTrigger>
<SelectContent>
<SelectItem value="general">General</SelectItem>
<SelectItem value="billing">Billing</SelectItem>
<SelectItem value="technical">Technical</SelectItem>
<SelectItem value="support">Support</SelectItem>
</SelectContent>
</Select>
</div>
<div className="grid grid-cols-4 items-center gap-4">
<Label htmlFor="status" className="text-right">
Status
</Label>
<Select
value={currentTicket.status}
onValueChange={(value) => handleSelectChange('status', value)}
>
<SelectTrigger className="col-span-3">
<SelectValue placeholder="Select status" />
</SelectTrigger>
<SelectContent>
<SelectItem value="open">Open</SelectItem>
<SelectItem value="in-progress">In Progress</SelectItem>
<SelectItem value="resolved">Resolved</SelectItem>
<SelectItem value="closed">Closed</SelectItem>
</SelectContent>
</Select>
</div>
<div className="grid grid-cols-4 items-center gap-4">
<Label htmlFor="priority" className="text-right">
Priority
</Label>
<Select
value={currentTicket.priority}
onValueChange={(value) => handleSelectChange('priority', value)}
>
<SelectTrigger className="col-span-3">
<SelectValue placeholder="Select priority" />
</SelectTrigger>
<SelectContent>
<SelectItem value="low">Low</SelectItem>
<SelectItem value="medium">Medium</SelectItem>
<SelectItem value="high">High</SelectItem>
</SelectContent>
</Select>
</div>
</div>
<DialogFooter>
<Button
variant="outline"
onClick={() => setIsDialogOpen(false)}
>
Cancel
</Button>
<Button onClick={saveTicket}>
{isEditing ? 'Update Ticket' : 'Create Ticket'}
</Button>
</DialogFooter>
</DialogContent>
</Dialog>
{/* Ticket Detail Dialog */}
<Dialog open={isDetailOpen} onOpenChange={setIsDetailOpen}>
<DialogContent className="max-w-3xl">
<DialogHeader>
<DialogTitle>{selectedTicket?.title}</DialogTitle>
<div className="flex gap-4 items-center">
<Badge variant={
selectedTicket?.status === 'open' ? 'default' :
selectedTicket?.status === 'in-progress' ? 'secondary' :
selectedTicket?.status === 'resolved' ? 'success' : 'outline'
}>
{selectedTicket?.status}
</Badge>
<Badge variant={
selectedTicket?.priority === 'low' ? 'success' :
selectedTicket?.priority === 'medium' ? 'warning' : 'destructive'
}>
{selectedTicket?.priority}
</Badge>
<span className="text-sm text-gray-500">
Created {formatDistanceToNow(new Date(selectedTicket?.createdAt || ''), { addSuffix: true })}
</span>
</div>
</DialogHeader>
<div className="py-4 max-h-[60vh] overflow-y-auto">
{renderMessages()}
</div>
{selectedTicket?.status !== 'closed' && (
<div className="flex gap-2">
<Input
value={newMessage}
onChange={(e) => setNewMessage(e.target.value)}
placeholder="Type your message..."
className="flex-1"
/>
<Button onClick={() => selectedTicket && addMessage(selectedTicket.id)}>
Send
</Button>
</div>
)}
</DialogContent>
</Dialog>
</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

@@ -0,0 +1,205 @@
import { Avatar, AvatarFallback, AvatarImage } from "./ui/avatar";
import { Button } from "./ui/button";
import { Card, CardContent, CardDescription, CardHeader, CardTitle, } from "./ui/card";
import { Input } from "./ui/input";
import { Label } from "./ui/label";
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue,} from "./ui/select";
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;
}
interface UserData {
success: boolean;
session_data: SessionData;
user_avatar: string;
}
export default function ProfilePage() {
const [userData, setUserData] = useState<UserData | null>(null);
const [invoiceList, setInvoiceList] = useState<any[]>([]);
const [error, setError] = useState<string | null>(null);
useEffect(() => {
const fetchSessionData = async () => {
try {
const response = await fetch(
'http://localhost:2058/host-api/v1/users/get-profile-data/',
{
credentials: 'include', // Crucial for cookies
headers: { 'Accept': 'application/json' }
}
);
const data = await response.json();
if (!response.ok || !data.success) {
throw new Error(data.error || 'Session fetch failed');
}
setUserData(data);
return data.session_data;
} catch (error) {
console.error('Fetch error:', error);
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) {
return <div>Error: {error}</div>;
}
if (!userData) {
return <div>Loading profile data...</div>;
}
return (
<div className="space-y-6 container mx-auto">
<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">
<Card>
<CardHeader>
<CardTitle>Personal Information</CardTitle>
<CardDescription>
Update your personal information and avatar.
</CardDescription>
</CardHeader>
<CardContent className="space-y-6">
<div className="flex items-center space-x-4">
<Avatar className="h-16 w-16">
<AvatarImage src={userData.session_data?.user_avatar} />
<AvatarFallback>JP</AvatarFallback>
</Avatar>
<UpdateAvatar />
</div>
<div className="grid grid-cols-1 gap-4 sm:grid-cols-2">
<div className="space-y-2">
<Label htmlFor="firstName">Full Name</Label>
<Input id="firstName" defaultValue={userData.session_data?.user_name || 'Jhon'} />
</div>
<div className="space-y-2">
<Label htmlFor="phone">Phone</Label>
<Input id="phone" defaultValue={userData.session_data?.user_vatar || ''} />
</div>
</div>
<div className="space-y-2">
<Label htmlFor="email">Email</Label>
<Input id="email" type="email" defaultValue={userData.session_data?.user_email || ''} />
</div>
</CardContent>
</Card>
<Card className="mt-6">
<CardHeader>
<CardTitle>Billing Information</CardTitle>
<CardDescription>
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>
</Card>
</div>
<div className="flex-1 space-y-6 lg:max-w-md">
<Card>
<CardHeader>
<CardTitle>Security</CardTitle>
<CardDescription>
Update your password and security settings.
</CardDescription>
</CardHeader>
<CardContent className="space-y-4">
<div className="space-y-2">
<Label htmlFor="currentPassword">Current password</Label>
<Input id="currentPassword" type="password" />
</div>
<div className="space-y-2">
<Label htmlFor="newPassword">New password</Label>
<Input id="newPassword" type="password" />
</div>
<div className="space-y-2">
<Label htmlFor="confirmPassword">Confirm password</Label>
<Input id="confirmPassword" type="password" />
</div>
<Button className="mt-4">Update password</Button>
</CardContent>
</Card>
<Card className="mt-6">
<CardHeader>
<CardTitle>Danger Zone</CardTitle>
<CardDescription>
These actions are irreversible. Proceed with caution.
</CardDescription>
</CardHeader>
<CardContent>
<div className="flex flex-col space-y-4">
<Button className="bg-red-500 hover:bg-red-600">Delete account</Button>
<Button variant="outline">Export data</Button>
</div>
</CardContent>
</Card>
</div>
</div>
</div>
);
}

View File

@@ -0,0 +1,43 @@
import React from "react";
interface Tab {
label: string;
value: string;
content: React.ReactNode;
}
interface CustomTabsProps {
tabs: Tab[];
defaultValue?: string;
onValueChange?: (value: string) => void;
}
export function CustomTabs({ tabs, defaultValue, onValueChange }: CustomTabsProps) {
const [activeTab, setActiveTab] = React.useState(defaultValue || tabs[0]?.value || '');
const handleTabChange = (value: string) => {
setActiveTab(value);
if (onValueChange) {
onValueChange(value);
}
};
return (
<div className="w-full">
<div className="flex border-b border-[#6d9e37]">
{tabs.map((tab) => (
<button
key={tab.value}
className={`px-4 py-2 font-medium text-sm focus:outline-none ${activeTab === tab.value ? 'border-x border-t border-[#6d9e37] text-[#6d9e37] rounded-t-md' : 'text-[#c7cccb]'}`}
onClick={() => handleTabChange(tab.value)}
>
{tab.label}
</button>
))}
</div>
<div className="py-4">
{tabs.find((tab) => tab.value === activeTab)?.content}
</div>
</div>
);
}

View File

@@ -0,0 +1,57 @@
import * as React from "react"
import { cn } from "../../lib/utils"
const Avatar = React.forwardRef<
HTMLSpanElement,
React.HTMLAttributes<HTMLSpanElement> & {
size?: "sm" | "md" | "lg"
}
>(({ className, size = "md", ...props }, ref) => {
const sizeClasses = {
sm: "h-8 w-8",
md: "h-10 w-10",
lg: "h-12 w-12",
};
return (
<span
ref={ref}
className={cn(
"relative flex shrink-0 overflow-hidden rounded-full",
sizeClasses[size],
className
)}
{...props}
/>
)
})
Avatar.displayName = "Avatar"
const AvatarImage = React.forwardRef<
HTMLImageElement,
React.ImgHTMLAttributes<HTMLImageElement>
>(({ className, ...props }, ref) => (
<img
ref={ref}
className={cn("aspect-square h-full w-full", className)}
{...props}
/>
))
AvatarImage.displayName = "AvatarImage"
const AvatarFallback = React.forwardRef<
HTMLSpanElement,
React.HTMLAttributes<HTMLSpanElement>
>(({ className, ...props }, ref) => (
<span
ref={ref}
className={cn(
"flex h-full w-full items-center justify-center rounded-full bg-muted",
className
)}
{...props}
/>
))
AvatarFallback.displayName = "AvatarFallback"
export { Avatar, AvatarImage, AvatarFallback }

View File

@@ -0,0 +1,40 @@
import * as React from "react";
import { cva, type VariantProps } from "class-variance-authority";
import { cn } from "../../lib/utils";
const badgeVariants = cva(
"inline-flex items-center rounded-full border px-2.5 py-0.5 text-xs font-semibold transition-colors focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2",
{
variants: {
variant: {
default:
"border-transparent bg-primary text-primary-foreground hover:bg-primary/80",
secondary:
"border-transparent bg-secondary text-secondary-foreground hover:bg-secondary/80",
destructive:
"border-transparent bg-destructive text-destructive-foreground hover:bg-destructive/80",
outline: "text-foreground",
success:
"border-transparent bg-green-100 text-green-800 dark:bg-green-900 dark:text-green-200",
warning:
"border-transparent bg-yellow-100 text-yellow-800 dark:bg-yellow-900 dark:text-yellow-200",
info: "border-transparent bg-blue-100 text-blue-800 dark:bg-blue-900 dark:text-blue-200",
},
},
defaultVariants: {
variant: "default",
},
}
);
export interface BadgeProps
extends React.HTMLAttributes<HTMLDivElement>,
VariantProps<typeof badgeVariants> {}
function Badge({ className, variant, ...props }: BadgeProps) {
return (
<div className={cn(badgeVariants({ variant }), className)} {...props} />
);
}
export { Badge, badgeVariants };

View File

@@ -8,7 +8,7 @@ export interface ButtonProps
}
const Button = React.forwardRef<HTMLButtonElement, ButtonProps>(
({ className, variant = "default", size = "default", ...props }, ref) => {
({ className, variant = "default", type = '', size = "default", ...props }, ref) => {
return (
<button
className={cn(

View File

@@ -8,7 +8,7 @@ const Card = React.forwardRef<
<div
ref={ref}
className={cn(
"rounded-lg border border-neutral-200 bg-white text-neutral-950 shadow-sm dark:border-neutral-800 dark:bg-neutral-950 dark:text-neutral-50",
"rounded-lg border border-[#6d9e37] bg-neutral-800 text-[#6d9e37] shadow-sm dark:border-neutral-800 dark:bg-neutral-950 dark:text-neutral-50",
className
)}
{...props}

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,104 @@
import * as React from "react";
// Utility function with proper error handling
export function customFormatDistanceToNow(
date: Date | string | number,
options: { addSuffix?: boolean } = { addSuffix: true }
): string {
try {
const dateObj = typeof date === "string" || typeof date === "number"
? new Date(date)
: date;
// Check for invalid date
if (isNaN(dateObj.getTime())) {
return "Invalid date";
}
const now = new Date();
const diffInSeconds = (now.getTime() - dateObj.getTime()) / 1000;
// Check for infinite or NaN values
if (!isFinite(diffInSeconds)) {
return "Invalid date range";
}
const rtf = new Intl.RelativeTimeFormat('en', { numeric: 'auto' });
const absoluteSeconds = Math.abs(diffInSeconds);
let value: number;
let unit: Intl.RelativeTimeFormatUnit;
if (absoluteSeconds < 60) {
value = Math.round(diffInSeconds);
unit = 'second';
} else if (absoluteSeconds < 3600) {
value = Math.round(diffInSeconds / 60);
unit = 'minute';
} else if (absoluteSeconds < 86400) {
value = Math.round(diffInSeconds / 3600);
unit = 'hour';
} else if (absoluteSeconds < 2592000) {
value = Math.round(diffInSeconds / 86400);
unit = 'day';
} else if (absoluteSeconds < 31536000) {
value = Math.round(diffInSeconds / 2592000);
unit = 'month';
} else {
value = Math.round(diffInSeconds / 31536000);
unit = 'year';
}
// Ensure value is finite before formatting
if (!isFinite(value)) {
return "Invalid date range";
}
let result = rtf.format(value, unit);
if (!options.addSuffix) {
result = result.replace(/^in /, '').replace(/ ago$/, '');
}
return result;
} catch (error) {
console.error("Error formatting date:", error);
return "Invalid date";
}
}
// React component interface
interface DateFormatProps {
date: Date | string | number;
className?: string;
addSuffix?: boolean;
fallback?: string;
}
// React component implementation
const DateFormat: React.FC<DateFormatProps> = ({
date,
className = "",
addSuffix = true,
fallback = "Invalid date",
}) => {
let formattedDate: string;
try {
formattedDate = customFormatDistanceToNow(date, { addSuffix });
} catch (error) {
formattedDate = fallback;
}
return (
<time
dateTime={new Date(date).toISOString()}
className={className}
title={new Date(date).toLocaleString()}
>
{formattedDate}
</time>
);
};
// Export both with distinct names
export { DateFormat, customFormatDistanceToNow as formatDistanceToNow };

View File

@@ -0,0 +1,122 @@
"use client"
import * as React from "react"
import * as DialogPrimitive from "@radix-ui/react-dialog"
import { X } from "lucide-react"
import { cn } from "../../lib/utils"
const Dialog = DialogPrimitive.Root
const DialogTrigger = DialogPrimitive.Trigger
const DialogPortal = DialogPrimitive.Portal
const DialogClose = DialogPrimitive.Close
const DialogOverlay = React.forwardRef<
React.ElementRef<typeof DialogPrimitive.Overlay>,
React.ComponentPropsWithoutRef<typeof DialogPrimitive.Overlay>
>(({ className, ...props }, ref) => (
<DialogPrimitive.Overlay
ref={ref}
className={cn(
"fixed inset-0 z-50 bg-black/80 data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0",
className
)}
{...props}
/>
))
DialogOverlay.displayName = DialogPrimitive.Overlay.displayName
const DialogContent = React.forwardRef<
React.ElementRef<typeof DialogPrimitive.Content>,
React.ComponentPropsWithoutRef<typeof DialogPrimitive.Content>
>(({ className, children, ...props }, ref) => (
<DialogPortal>
<DialogOverlay />
<DialogPrimitive.Content
ref={ref}
className={cn(
"fixed left-[50%] top-[50%] z-50 grid w-full max-w-lg translate-x-[-50%] translate-y-[-50%] gap-4 border bg-[#fff] p-6 shadow-lg duration-200 data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[state=closed]:slide-out-to-left-1/2 data-[state=closed]:slide-out-to-top-[48%] data-[state=open]:slide-in-from-left-1/2 data-[state=open]:slide-in-from-top-[48%] sm:rounded-lg",
className
)}
{...props}
>
{children}
<DialogPrimitive.Close className="absolute right-4 top-4 rounded-sm opacity-70 ring-offset-[#fff] transition-opacity hover:opacity-100 focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2 disabled:pointer-events-none data-[state=open]:bg-accent data-[state=open]:text-muted-foreground">
<X className="h-4 w-4 text-black" />
<span className="sr-only">Close</span>
</DialogPrimitive.Close>
</DialogPrimitive.Content>
</DialogPortal>
))
DialogContent.displayName = DialogPrimitive.Content.displayName
const DialogHeader = ({
className,
...props
}: React.HTMLAttributes<HTMLDivElement>) => (
<div
className={cn(
"flex flex-col space-y-1.5 text-center sm:text-left",
className
)}
{...props}
/>
)
DialogHeader.displayName = "DialogHeader"
const DialogFooter = ({
className,
...props
}: React.HTMLAttributes<HTMLDivElement>) => (
<div
className={cn(
"flex flex-col-reverse sm:flex-row sm:justify-end sm:space-x-2",
className
)}
{...props}
/>
)
DialogFooter.displayName = "DialogFooter"
const DialogTitle = React.forwardRef<
React.ElementRef<typeof DialogPrimitive.Title>,
React.ComponentPropsWithoutRef<typeof DialogPrimitive.Title>
>(({ className, ...props }, ref) => (
<DialogPrimitive.Title
ref={ref}
className={cn(
"text-lg font-semibold leading-none tracking-tight text-[#6d9e37]",
className
)}
{...props}
/>
))
DialogTitle.displayName = DialogPrimitive.Title.displayName
const DialogDescription = React.forwardRef<
React.ElementRef<typeof DialogPrimitive.Description>,
React.ComponentPropsWithoutRef<typeof DialogPrimitive.Description>
>(({ className, ...props }, ref) => (
<DialogPrimitive.Description
ref={ref}
className={cn("text-sm text-neutral-400", className)}
{...props}
/>
))
DialogDescription.displayName = DialogPrimitive.Description.displayName
export {
Dialog,
DialogPortal,
DialogOverlay,
DialogTrigger,
DialogClose,
DialogContent,
DialogHeader,
DialogFooter,
DialogTitle,
DialogDescription,
}

View File

@@ -10,7 +10,7 @@ const Input = React.forwardRef<HTMLInputElement, InputProps>(
<input
type={type}
className={cn(
"flex h-10 w-full rounded-md border border-neutral-600 bg-neutral-800 px-3 py-2 text-sm ring-offset-neutral-900 file:border-0 file:bg-transparent file:text-sm file:font-medium placeholder:text-neutral-400 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-[#6d9e37] focus-visible:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50",
"flex h-10 w-full rounded-md border border-neutral-600 bg-neutral-800 px-3 py-2 text-sm ring-offset-neutral-900 file:border-0 file:bg-transparent file:text-sm file:font-medium placeholder:text-neutral-400 focus-visible:outline-none focus-visible:ring-[#6d9e37] focus-visible:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50",
className
)}
ref={ref}
@@ -22,3 +22,4 @@ const Input = React.forwardRef<HTMLInputElement, InputProps>(
Input.displayName = "Input";
export { Input };

View File

@@ -1,25 +1,130 @@
import * as React from "react";
import * as SelectPrimitive from "@radix-ui/react-select";
import { cn } from "../../lib/utils";
export interface SelectProps
extends React.SelectHTMLAttributes<HTMLSelectElement> {}
const Select = SelectPrimitive.Root;
const Select = React.forwardRef<HTMLSelectElement, SelectProps>(
({ className, children, ...props }, ref) => {
return (
<select
className={cn(
"flex h-10 w-full rounded-md border border-neutral-600 bg-neutral-800 px-3 py-2 text-sm ring-offset-neutral-900 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-[#6d9e37] focus-visible:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50",
className
)}
ref={ref}
{...props}
>
const SelectGroup = SelectPrimitive.Group;
const SelectValue = SelectPrimitive.Value;
const SelectTrigger = React.forwardRef<
React.ElementRef<typeof SelectPrimitive.Trigger>,
React.ComponentPropsWithoutRef<typeof SelectPrimitive.Trigger>
>(({ className, children, ...props }, ref) => (
<SelectPrimitive.Trigger
ref={ref}
className={cn(
"flex h-10 w-full items-center justify-between rounded-md border border-neutral-600 bg-neutral-800 px-3 py-2 text-sm",
"focus:outline-none focus:ring-2 focus:ring-[#6d9e37] focus:ring-offset-2 focus:ring-offset-neutral-900",
"disabled:cursor-not-allowed disabled:opacity-50",
className
)}
{...props}
>
{children}
<SelectPrimitive.Icon asChild>
<ChevronDownIcon className="h-4 w-4 opacity-50" />
</SelectPrimitive.Icon>
</SelectPrimitive.Trigger>
));
SelectTrigger.displayName = SelectPrimitive.Trigger.displayName;
const SelectContent = React.forwardRef<
React.ElementRef<typeof SelectPrimitive.Content>,
React.ComponentPropsWithoutRef<typeof SelectPrimitive.Content>
>(({ className, children, ...props }, ref) => (
<SelectPrimitive.Portal>
<SelectPrimitive.Content
ref={ref}
className={cn(
"relative z-50 min-w-[8rem] overflow-hidden rounded-md border border-neutral-600 bg-neutral-800 shadow-md animate-in fade-in-80",
className
)}
{...props}
>
<SelectPrimitive.Viewport className="p-1">
{children}
</select>
);
}
);
Select.displayName = "Select";
</SelectPrimitive.Viewport>
</SelectPrimitive.Content>
</SelectPrimitive.Portal>
));
SelectContent.displayName = SelectPrimitive.Content.displayName;
export { Select };
const SelectLabel = React.forwardRef<
React.ElementRef<typeof SelectPrimitive.Label>,
React.ComponentPropsWithoutRef<typeof SelectPrimitive.Label>
>(({ className, ...props }, ref) => (
<SelectPrimitive.Label
ref={ref}
className={cn("py-1.5 pl-8 pr-2 text-sm font-semibold", className)}
{...props}
/>
));
SelectLabel.displayName = SelectPrimitive.Label.displayName;
const SelectItem = React.forwardRef<
React.ElementRef<typeof SelectPrimitive.Item>,
React.ComponentPropsWithoutRef<typeof SelectPrimitive.Item>
>(({ className, children, ...props }, ref) => (
<SelectPrimitive.Item
ref={ref}
className={cn(
"relative flex cursor-default select-none items-center rounded-sm py-1.5 pl-8 pr-2 text-sm outline-none",
"focus:bg-neutral-700 focus:text-white",
"data-[disabled]:pointer-events-none data-[disabled]:opacity-50",
className
)}
{...props}
>
<SelectPrimitive.ItemText>{children}</SelectPrimitive.ItemText>
</SelectPrimitive.Item>
));
SelectItem.displayName = SelectPrimitive.Item.displayName;
const SelectSeparator = React.forwardRef<
React.ElementRef<typeof SelectPrimitive.Separator>,
React.ComponentPropsWithoutRef<typeof SelectPrimitive.Separator>
>(({ className, ...props }, ref) => (
<SelectPrimitive.Separator
ref={ref}
className={cn("-mx-1 my-1 h-px bg-neutral-600", className)}
{...props}
/>
));
SelectSeparator.displayName = SelectPrimitive.Separator.displayName;
// ChevronDownIcon component
const ChevronDownIcon = React.forwardRef<
SVGSVGElement,
React.SVGProps<SVGSVGElement>
>((props, ref) => (
<svg
ref={ref}
width="15"
height="15"
viewBox="0 0 15 15"
fill="none"
xmlns="http://www.w3.org/2000/svg"
{...props}
>
<path
d="M3.13523 6.15803C3.3241 5.95657 3.64052 5.94637 3.84197 6.13523L7.5 9.56464L11.158 6.13523C11.3595 5.94637 11.6759 5.95657 11.8648 6.15803C12.0536 6.35949 12.0434 6.67591 11.842 6.86477L7.84197 10.6148C7.64964 10.7951 7.35036 10.7951 7.15803 10.6148L3.15803 6.86477C2.95657 6.67591 2.94637 6.35949 3.13523 6.15803Z"
fill="currentColor"
fillRule="evenodd"
clipRule="evenodd"
></path>
</svg>
));
ChevronDownIcon.displayName = "ChevronDownIcon";
export {
Select,
SelectGroup,
SelectValue,
SelectTrigger,
SelectContent,
SelectLabel,
SelectItem,
SelectSeparator,
};

View File

@@ -0,0 +1,23 @@
import * as React from "react"
import { cn } from "../../lib/utils"
const Separator = React.forwardRef<
HTMLDivElement,
React.HTMLAttributes<HTMLDivElement> & {
orientation?: "horizontal" | "vertical"
}
>(({ className, orientation = "horizontal", ...props }, ref) => (
<div
ref={ref}
className={cn(
"shrink-0 bg-border",
orientation === "horizontal" ? "h-[1px] w-full" : "h-full w-[1px]",
className
)}
role="separator"
{...props}
/>
))
Separator.displayName = "Separator"
export { Separator }

View File

@@ -0,0 +1,62 @@
interface TableProps {
headers: string[];
data: any[];
className?: string;
caption?: string;
striped?: boolean;
hover?: boolean;
}
const Table: React.FC<TableProps> = ({
headers,
data,
className = '',
caption,
striped = false,
hover = false
}) => {
return (
<div className={`overflow-x-auto ${className}`}>
<table className="min-w-full bg-white border border-gray-200 rounded-lg">
{caption && (
<caption className="text-sm text-gray-500 p-2 text-left">
{caption}
</caption>
)}
<thead className="bg-gray-50">
<tr>
{headers.map((header, index) => (
<th
key={index}
className="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider"
>
{header}
</th>
))}
</tr>
</thead>
<tbody className="divide-y divide-gray-200">
{data.map((row, index) => (
<tr
key={index}
className={`${striped && index % 2 === 0 ? 'bg-gray-50' : ''} ${
hover ? 'hover:bg-gray-100' : ''
}`}
>
{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>
))}
</tbody>
</table>
</div>
);
};
export default Table;

View File

@@ -0,0 +1,33 @@
import * as React from "react";
import * as Tabs from "@radix-ui/react-tabs";
import { cn } from "../../lib/utils";
export interface CustomTabsProps {
tabs: { label: string; value: string; content: React.ReactNode }[];
className?: string;
}
const CustomTabs: React.FC<CustomTabsProps> = ({ tabs, className }) => {
return (
<Tabs.Root defaultValue={tabs[0]?.value} className={cn("w-full", className)}>
<Tabs.List className="flex border-b border-neutral-600 bg-neutral-800">
{tabs.map((tab) => (
<Tabs.Trigger
key={tab.value}
value={tab.value}
className="px-4 py-2 text-sm text-neutral-400 hover:text-white focus-visible:ring-2 focus-visible:ring-[#6d9e37]"
>
{tab.label}
</Tabs.Trigger>
))}
</Tabs.List>
{tabs.map((tab) => (
<Tabs.Content key={tab.value} value={tab.value} className="p-4 text-neutral-300">
{tab.content}
</Tabs.Content>
))}
</Tabs.Root>
);
};
export { CustomTabs };

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;

View File

@@ -174,8 +174,8 @@ const organizationSchema = {
<ul class="space-y-2 text-neutral-400">
<li class="flex items-center gap-2">📦 <a href="/services" class="hover:text-[#6d9e37] transition-colors">All Services</a></li>
<li class="flex items-center gap-2">🚀 <a href="/get-started" class="hover:text-[#6d9e37] transition-colors">Get Started</a></li>
<li class="flex items-center gap-2">👨‍💻 <a href="/services/hire-developer" class="hover:text-[#6d9e37] transition-colors">Hire a Human Developer</a></li>
<li class="flex items-center gap-2">🤖 <a href="/services/hire-ai-agent" class="hover:text-[#6d9e37] transition-colors">Hire an AI Agent</a></li>
<li class="flex items-center gap-2">👨‍💻 <a href="/hire-developer" class="hover:text-[#6d9e37] transition-colors">Hire a Human Developer</a></li>
<li class="flex items-center gap-2">🤖 <a href="/hire-ai-agent" class="hover:text-[#6d9e37] transition-colors">Hire an AI Agent</a></li>
</ul>
</div>

37
src/lib/localizeTime.tsx Normal file
View File

@@ -0,0 +1,37 @@
export function localizeTime(timeValue: string, targetTimeZone?: string): string {
// 1. Auto-detect user's timezone if none provided
if (!targetTimeZone) {
targetTimeZone = Intl.DateTimeFormat().resolvedOptions().timeZone;
}
// 2. Format the date in the target timezone
const date = new Date(timeValue.replace(' ', 'T') + 'Z'); // Ensure UTC
const formatter = new Intl.DateTimeFormat('en-US', {
timeZone: targetTimeZone,
year: 'numeric',
month: '2-digit',
day: '2-digit',
hour: '2-digit',
minute: '2-digit',
second: '2-digit',
hour12: false
});
const [
{ value: month },,
{ value: day },,
{ value: year },,
{ value: hour },,
{ value: minute },,
{ value: second }
] = formatter.formatToParts(date);
return `${year}-${month}-${day} ${hour}:${minute}:${second}`;
}
// Usage:
// const localTime = localizeTime(ticket.created_at); // Auto-detects timezone
// console.log(localTime); // "2025-04-03 20:14:50" (in user's local time)
// const timeInTokyo = localizeTime(ticket.created_at, "Asia/Tokyo");
// console.log(timeInTokyo); // "2025-04-04 05:14:50" (Tokyo time)

View File

@@ -4,3 +4,9 @@ import { twMerge } from 'tailwind-merge';
export function cn(...inputs: ClassValue[]) {
return twMerge(clsx(inputs));
}
// Type helpers
export type MergeElementProps<
T extends React.ElementType,
P extends object = {}
> = Omit<React.ComponentPropsWithRef<T>, keyof P> & P;

View File

@@ -7,7 +7,7 @@ import Layout from "../../layouts/Layout.astro"
</div>
<script is:inline>
fetch('/host-api/add-domain/')
fetch('http://localhost:2058/host-api/v1/add-domain/')
.then(response => {
if (!response.ok) throw new Error('Network error');
return response.json();

View File

@@ -20,7 +20,7 @@ import Layout from "../../layouts/Layout.astro"
responseElement.textContent = "Deleting domain...";
try {
const response = await fetch('/host-api/delete-domain/', {
const response = await fetch('http://localhost:2058/host-api/v1/delete-domain/', {
method: 'POST',
headers: {
'Content-Type': 'application/x-www-form-urlencoded',

View File

@@ -7,7 +7,7 @@ import Layout from "../../layouts/Layout.astro"
</div>
<script is:inline>
fetch('/host-api/list-domain/')
fetch('http://localhost:2058/host-api/v1/list-domain/')
.then(response => {
if (!response.ok) throw new Error('Network error');
return response.json();

View File

@@ -5,7 +5,7 @@ import { DomainSetupForm } from '../components/DomainSetupForm';
// Page-specific SEO metadata
const pageTitle = "Get Started | SiliconPin";
const pageDescription = "Start your project with SiliconPin's hosting services. Deploy a web app, from source code, or upload a static site.";
const pageImage = "https://images.unsplash.com/photo-1551731409-43eb3e517a1a?q=80&w=2000&auto=format&fit=crop";
const pageImage = "https://siliconpin.com/assets/images/get-started-og-image.png";
// Generate a random subdomain for server side rendering
const randomString = () => {
@@ -24,7 +24,7 @@ const defaultSubdomain = randomString();
<div class="text-center mb-8 sm:mb-12">
<h1 class="text-3xl sm:text-4xl font-bold text-[#6d9e37] mb-3 sm:mb-4">Get Started</h1>
<p class="text-lg sm:text-xl max-w-3xl mx-auto text-neutral-300">
Launch your project with SiliconPin's high-performance hosting services
Launch your project with SiliconPin's easy and reliable hosting services
</p>
</div>

View File

@@ -0,0 +1,9 @@
---
import Layout from "../layouts/Layout.astro";
import HireAIAgent from "../components/HireAIAgent";
---
<Layout title="">
<div>
<HireAIAgent client:load />
</div>
</Layout>

View File

@@ -0,0 +1,9 @@
---
import Layout from "../layouts/Layout.astro";
import Developer from "../components/HireDeveloper";
---
<Layout title="">
<div>
<Developer client:load />
</div>
</Layout>

7
src/pages/login.astro Normal file
View File

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

265
src/pages/login_old.astro Normal file
View File

@@ -0,0 +1,265 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>PocketBase Sign-In</title>
</head>
<style>
.login-main-conatiner{
display: flex;
justify-content: center;
}
.login-container{
max-width: 380px;
width: 100%;
background: #222;
padding: 40px;
border-radius: 12px;
box-shadow: 0 8px 16px rgba(0, 0, 0, 0.3);
display: flex;
flex-direction: column;
margin: 100px 0;
}
#loginForm{
display: flex;
flex-direction: column;
gap: 10px;
}
.auth-title {
color: #ffffff;
font-size: 24px;
font-weight: 600;
margin-bottom: 20px;
text-align: center;
}
.auth-input {
background: #333;
border: 1px solid #444;
color: #fff;
border-radius: 8px;
outline: none;
font-size: 15px;
transition: border 0.3s;
}
.auth-input:focus {
border-color: #00bcd4;
}
.auth-btn {
background: linear-gradient(135deg, #00bcd4, #0288d1);
color: white;
padding: 14px;
width: 100%;
border: none;
border-radius: 8px;
cursor: pointer;
font-size: 16px;
font-weight: 500;
margin-top: 10px;
transition: background 0.3s ease;
}
.auth-btn:hover {
background: linear-gradient(135deg, #0288d1, #00bcd4);
}
.auth-social {
display: flex;
flex-direction: column;
gap: 10px;
}
.auth-social-btn {
display: flex;
align-items: center;
justify-content: center;
padding: 12px;
border: none;
border-radius: 8px;
cursor: pointer;
font-size: 14px;
font-weight: 500;
color: white;
transition: opacity 0.3s;
width: 100%;
margin-top: 10px;
}
.auth-social-btn:hover {
opacity: 0.8;
}
.auth-google {
background: #db4437;
}
.auth-facebook {
background: #1877f2;
}
.auth-github {
background: #333;
}
.auth-divider {
margin: 20px 0;
color: #777;
font-size: 14px;
position: relative;
text-align: center;
}
.auth-divider::before,
.auth-divider::after {
content: "";
position: absolute;
width: 37%;
height: 1px;
background: #444;
top: 50%;
}
.auth-divider::before {
left: 0;
}
.auth-divider::after {
right: 0;
}
.auth-error {
color: #ff4c4c;
font-size: 14px;
margin-top: 10px;
text-align: center;
}
.auth-footer {
margin-top: 15px;
font-size: 13px;
color: #999;
text-align: center;
}
.auth-footer a {
color: #00bcd4;
text-decoration: none;
}
.auth-footer a:hover {
text-decoration: underline;
}
.pass-view-button{
border: none;
background: transparent;
font-size: 10px;
}
</style>
<body>
<div class="login-main-conatiner">
<div class="login-container">
<h2 class="auth-title">Login to Your Account</h2>
<form id="loginForm">
<input class="auth-input" id="email" type="email" name="email" placeholder="Email Address" required value="suvodip@siliconpin.com">
<div class="auth-input" style="display: flex; position: relative;">
<input class="auth-input" id="password" type="text" name="password" placeholder="Password" required style="width: 100%;" value="Simple2pass" />
<button onclick="toggleInputType();" type="button" class="pass-view-button" style="position: absolute; right: 0; margin-top: 3px;">
<img src="/assets/eye.svg" id="eyeToggle" alt="">
</button>
</div>
<button class="auth-btn" type="submit">Sign In</button>
</form>
<p class="auth-divider">Or continue with</p>
<div class="">
<button id="loginGoogleBtn" onclick="loginWithOAuth2('google')" class="auth-social-btn auth-google">Login with Google</button>
<button id="loginFacebookBtn" onclick="loginWithOAuth2('facebook')" class="auth-social-btn auth-facebook">Login with Facebook</button>
<button id="loginGitHubBtn" onclick="loginWithOAuth2('github')" class="auth-social-btn auth-github">Login with GitHub</button>
</div>
<p id="status" style="display: none;"></p>
<p class="auth-footer">Don't have an account? <a href="/sign-up">Sign up</a></p>
</div>
</div>
<script is:inline type="module">
import PocketBase from 'https://cdn.jsdelivr.net/npm/pocketbase@0.19.0/+esm';
const pb = new PocketBase("https://tst-pb.s38.siliconpin.com");
let isAuthenticated = false;
document.getElementById("loginForm").addEventListener("submit", async function(event) {
event.preventDefault();
const email = document.getElementById("email").value;
const password = document.getElementById("password").value;
const status = document.getElementById("status");
try {
const authData = await pb.collection("users").authWithPassword(email, password);
console.log("User signed in:", authData);
status.style.display = 'block';
status.textContent = "Login successful!";
status.style.color = "green";
isAuthenticated = true;
updateUI(authData.record, authData.token);
window.location.href = '/profile';
} catch (error) {
console.error("Login failed:", error);
status.style.display = 'block';
status.textContent = "Login failed. Please check your credentials.";
status.style.color = "red";
}
});
async function loginWithOAuth2(provider) {
try {
const authData = await pb.collection('users').authWithOAuth2({ provider: provider });
if (!authData || !authData.record) {
console.error("Login failed: No user record found.");
return;
}
let accessToken = authData.token;
isAuthenticated = true;
console.log("Google Auth Response:", authData);
updateUI(authData.record, authData.token);
window.location.href = '/profile';
} catch (error) {
console.error("Google Login failed:", error);
}
}
function updateUI(user, token) {
if (!user || !user.email) {
console.error("User data is missing:", user);
return;
}
// Send user data to PHP session
fetch('http://localhost:2058/host-api/v1/users/session/', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
accessToken: token,
email: user.email,
name: user.name,
avatar: user.avatar ? pb.files.getUrl(user, user.avatar) : '',
isAuthenticated : true,
id: user.id
})
})
.then(response => response.json())
.then(data => console.log('Session updated:', data))
.catch(error => console.error('Error saving session:', error));
}
window.loginWithOAuth2 = loginWithOAuth2;
if (pb.authStore.isValid) {
updateUI(pb.authStore.model);
}
const inputType = document.getElementById('password');
const eyeToggle = document.getElementById('eyeToggle');
function toggleInputType(){
if(inputType.type === 'password'){
inputType.type = 'text';
eyeToggle.src = '/assets/eye-close.svg';
}else{
inputType.type = 'password';
eyeToggle.src = '/assets/eye.svg';
}
}
window.toggleInputType = toggleInputType;
</script>
</body>
</html>

View File

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

View File

@@ -1,10 +1,12 @@
---
import Layout from "../layouts/Layout.astro";
const phpHello = `$_SESSION['userName']`;
// const phpHello = `$_SESSION['userName']`;
import UserProfile from "../components/UserProfile";
---
<Layout title="Profile Page">
<div class="flex items-center justify-center min-h-screen bg-gray-700">
<UserProfile client:load />
<!-- <div class="flex items-center justify-center min-h-screen bg-gray-700">
<div class="w-96 p-6 shadow-lg rounded-lg bg-white text-center">
<img class="w-24 h-24 rounded-full border-4 border-blue-500 mx-auto" src="/profile.jpg" alt="Profile Picture" />
<h2 class="text-2xl font-semibold mt-4" set:html={phpHello ? phpHello : 'User Name'}></h2>
@@ -15,5 +17,5 @@ const phpHello = `$_SESSION['userName']`;
<button class="bg-gray-200 text-black px-4 py-2 rounded-lg">Message</button>
</div>
</div>
</div>
</div> -->
</Layout>

View File

@@ -10,43 +10,25 @@ const pageImage = "https://images.unsplash.com/photo-1551731409-43eb3e517a1a?q=8
// Service data
const services = [
{
title: 'PHP Hosting',
description: 'Fast, secure, and reliable PHP hosting solutions for your web applications.',
title: 'Deploy an App',
description: 'WordPress, Joomla, Drupal, PrestaShop, Wiki, Moodle, Directus, PocketBase, StarAPI and more.',
imageUrl: 'https://images.unsplash.com/photo-1599507593499-a3f7d7d97667?q=80&w=2000&auto=format&fit=crop',
features: [
'PHP 8.x support',
'One-click installation of popular CMS',
'Free SSL certificates',
'Optimized for WordPress, Laravel, etc.',
'24/7 Technical support'
],
learnMoreUrl: '/services/php'
features: [ 'WordPress', 'Joomla', 'Drupal', 'PrestaShop', 'Wiki', 'Moodle', 'Directus', 'PocketBase', 'StarAPI' ],
learnMoreUrl: '/services/deploy-an-app'
},
{
title: 'Node.js Hosting',
description: 'High-performance Node.js hosting with seamless deployment pipelines.',
imageUrl: 'https://images.unsplash.com/photo-1570063578733-6a33b69d1538?q=80&w=2000&auto=format&fit=crop',
features: [
'Latest Node.js versions',
'NPM/Yarn support',
'Express, Next.js, and more',
'Managed SSL certificates',
'Automatic scaling'
],
learnMoreUrl: '/services/nodejs'
title: 'Deploy From Source Code',
description: 'Node.js, Python, Ruby, Go, Rust, and more. Deploy your custom applications with ease.',
imageUrl: 'https://images.unsplash.com/photo-1599507593499-a3f7d7d97667?q=80&w=2000&auto=format&fit=crop',
features: ['Node.js', 'Python', 'Ruby', 'Go', 'Rust', 'Docker', 'Kubernetes', 'JAMstack', 'Serverless'],
learnMoreUrl: '/services/deploy-from-source-code'
},
{
title: 'Python Hosting',
description: 'Scalable Python hosting for web applications, APIs, and data science projects.',
title: 'Static Site Hosting',
description: 'Secure and scalable hosting for static websites and JAMstack applications.',
imageUrl: 'https://images.unsplash.com/photo-1526379879527-8559ecfcaec0?q=80&w=2000&auto=format&fit=crop',
features: [
'Python 3.x support',
'Django, Flask, and FastAPI ready',
'Virtual environments',
'Jupyter notebook integration',
'Seamless deployment'
],
learnMoreUrl: '/services/python'
features: ['JAMstack', 'Gatsby', 'Hugo', 'Next.js', 'Nuxt.js', 'VuePress', 'Eleventy', 'SvelteKit', 'Astro'],
learnMoreUrl: '/services/static-site-hosting'
},
{
title: 'Kubernetes (K8s)',
@@ -73,6 +55,20 @@ const services = [
'Simplified management'
],
learnMoreUrl: '/services/k3s'
},
{
title: 'Hire a Human Developer',
description: 'Need a custom solution? Our experts can design a tailored App or WebApp for your specific needs.',
imageUrl: 'https://images.unsplash.com/photo-1558494949-ef010cbdcc31?q=80&w=2000&auto=format&fit=crop',
features: ['Node.js', 'Python', 'Ruby', 'Go', 'Rust', 'Docker', 'Kubernetes', 'JAMstack', 'Serverless'],
learnMoreUrl: '/services/hire-a-human-developer'
},
{
title: 'Hire an AI Agent',
description: 'Need a custom solution? Our experts can design a tailored AI Agent for your specific needs.',
imageUrl: 'https://images.unsplash.com/photo-1558494949-ef010cbdcc31?q=80&w=2000&auto=format&fit=crop',
features: ['Reactive Agents (Stateless, Rule-Based)', 'Proactive Agents (Stateful, Machine Learning)', 'Hybrid Agents (Reactive + Proactive)', 'Model-Based Agents (Stateful, Uses Memory)', 'Goal-Based Agents (Optimizes for an Objective)', 'Utility-Based Agents', 'Self Learning Agents', 'Autonomous AI Agents'],
learnMoreUrl: '/services/hire-an-ai-agent'
}
];
---

7
src/pages/ticket.astro Normal file
View File

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

3751
yarn.lock Normal file

File diff suppressed because it is too large Load Diff