Compare commits
2 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| f118ca8489 | |||
| f7dbc170c8 |
+6
-8
@@ -15,19 +15,17 @@
|
|||||||
"cors": "latest",
|
"cors": "latest",
|
||||||
"dotenv": "latest",
|
"dotenv": "latest",
|
||||||
"express": "latest",
|
"express": "latest",
|
||||||
"gsap": "^3.15.0",
|
|
||||||
"jsonwebtoken": "latest",
|
"jsonwebtoken": "latest",
|
||||||
"lucide-vue-next": "latest",
|
|
||||||
"motion-v": "^1.10.3",
|
|
||||||
"multer": "latest",
|
|
||||||
"mysql2": "latest",
|
|
||||||
"vite": "latest",
|
"vite": "latest",
|
||||||
"vue": "latest",
|
"vue": "latest",
|
||||||
"vue-router": "latest"
|
"vue-router": "latest",
|
||||||
|
"lucide-vue-next": "latest",
|
||||||
|
"multer": "latest",
|
||||||
|
"mysql2": "latest"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"autoprefixer": "latest",
|
"tailwindcss": "^3.4.17",
|
||||||
"postcss": "latest",
|
"postcss": "latest",
|
||||||
"tailwindcss": "^3.4.17"
|
"autoprefixer": "latest"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Generated
+2
-98
@@ -23,18 +23,12 @@ importers:
|
|||||||
express:
|
express:
|
||||||
specifier: latest
|
specifier: latest
|
||||||
version: 5.2.1
|
version: 5.2.1
|
||||||
gsap:
|
|
||||||
specifier: ^3.15.0
|
|
||||||
version: 3.15.0
|
|
||||||
jsonwebtoken:
|
jsonwebtoken:
|
||||||
specifier: latest
|
specifier: latest
|
||||||
version: 9.0.3
|
version: 9.0.3
|
||||||
lucide-vue-next:
|
lucide-vue-next:
|
||||||
specifier: latest
|
specifier: latest
|
||||||
version: 1.0.0(vue@3.5.34)
|
version: 1.0.0(vue@3.5.34)
|
||||||
motion-v:
|
|
||||||
specifier: ^1.10.3
|
|
||||||
version: 1.10.3(@vueuse/core@14.3.0(vue@3.5.34))(vue@3.5.34)
|
|
||||||
multer:
|
multer:
|
||||||
specifier: latest
|
specifier: latest
|
||||||
version: 2.1.1
|
version: 2.1.1
|
||||||
@@ -241,9 +235,6 @@ packages:
|
|||||||
'@types/node@25.7.0':
|
'@types/node@25.7.0':
|
||||||
resolution: {integrity: sha512-z+pdZyxE+RTQE9AcboAZCb4otwcrvgHD+GlBpPgn0emDVt0ohrTMhAwlr2Wd9nZ+nihhYFxO2pThz3C5qSu2Eg==}
|
resolution: {integrity: sha512-z+pdZyxE+RTQE9AcboAZCb4otwcrvgHD+GlBpPgn0emDVt0ohrTMhAwlr2Wd9nZ+nihhYFxO2pThz3C5qSu2Eg==}
|
||||||
|
|
||||||
'@types/web-bluetooth@0.0.21':
|
|
||||||
resolution: {integrity: sha512-oIQLCGWtcFZy2JW77j9k8nHzAOpqMHLQejDA48XXMWH6tjCQHz5RCFz1bzsmROyL6PUm+LLnUiI4BCn221inxA==}
|
|
||||||
|
|
||||||
'@vitejs/plugin-vue@6.0.6':
|
'@vitejs/plugin-vue@6.0.6':
|
||||||
resolution: {integrity: sha512-u9HHgfrq3AjXlysn0eINFnWQOJQLO9WN6VprZ8FXl7A2bYisv3Hui9Ij+7QZ41F/WYWarHjwBbXtD7dKg3uxbg==}
|
resolution: {integrity: sha512-u9HHgfrq3AjXlysn0eINFnWQOJQLO9WN6VprZ8FXl7A2bYisv3Hui9Ij+7QZ41F/WYWarHjwBbXtD7dKg3uxbg==}
|
||||||
engines: {node: ^20.19.0 || >=22.12.0}
|
engines: {node: ^20.19.0 || >=22.12.0}
|
||||||
@@ -298,19 +289,6 @@ packages:
|
|||||||
'@vue/shared@3.5.34':
|
'@vue/shared@3.5.34':
|
||||||
resolution: {integrity: sha512-24uqU4OIiX29ryC3MeWid/Xf2fa2EFRUVLb77nRhk+UrTVrh/XiGtFAFmJBAtBRbjwNdsPRP+jj/OL27Eg1NDA==}
|
resolution: {integrity: sha512-24uqU4OIiX29ryC3MeWid/Xf2fa2EFRUVLb77nRhk+UrTVrh/XiGtFAFmJBAtBRbjwNdsPRP+jj/OL27Eg1NDA==}
|
||||||
|
|
||||||
'@vueuse/core@14.3.0':
|
|
||||||
resolution: {integrity: sha512-aHfz47g0ZhMtTVHmIzMVpJy8ePhhOy68GY5bv110+5DVtZ+W7BsOx+m61UNQqfrWyPztIHIanWa3E2tib3NFIw==}
|
|
||||||
peerDependencies:
|
|
||||||
vue: ^3.5.0
|
|
||||||
|
|
||||||
'@vueuse/metadata@14.3.0':
|
|
||||||
resolution: {integrity: sha512-BwxmbAzwAVF50+MW57GXOUEV61nFBGnlBvrTqj49PqWJu3uw7hdu72ztXeZ33RdZtDY6kO+bfCAE1PCn88Tktw==}
|
|
||||||
|
|
||||||
'@vueuse/shared@14.3.0':
|
|
||||||
resolution: {integrity: sha512-bZpge9eSXwa4ToSiqJ7j6KRwhAsneMFoSz3LMWKQDkqimm3D/tbFlrklrs/IOqC8tEcYmXQZJ6N0UrjhBirVCg==}
|
|
||||||
peerDependencies:
|
|
||||||
vue: ^3.5.0
|
|
||||||
|
|
||||||
accepts@2.0.0:
|
accepts@2.0.0:
|
||||||
resolution: {integrity: sha512-5cvg6CtKwfgdmVqY1WIiXKc3Q1bkRqGLi+2W/6ao+6Y7gu/RCwRuAhGEzh5B4KlszSuTLgZYuqFqo5bImjNKng==}
|
resolution: {integrity: sha512-5cvg6CtKwfgdmVqY1WIiXKc3Q1bkRqGLi+2W/6ao+6Y7gu/RCwRuAhGEzh5B4KlszSuTLgZYuqFqo5bImjNKng==}
|
||||||
engines: {node: '>= 0.6'}
|
engines: {node: '>= 0.6'}
|
||||||
@@ -576,20 +554,6 @@ packages:
|
|||||||
fraction.js@5.3.4:
|
fraction.js@5.3.4:
|
||||||
resolution: {integrity: sha512-1X1NTtiJphryn/uLQz3whtY6jK3fTqoE3ohKs0tT+Ujr1W59oopxmoEh7Lu5p6vBaPbgoM0bzveAW4Qi5RyWDQ==}
|
resolution: {integrity: sha512-1X1NTtiJphryn/uLQz3whtY6jK3fTqoE3ohKs0tT+Ujr1W59oopxmoEh7Lu5p6vBaPbgoM0bzveAW4Qi5RyWDQ==}
|
||||||
|
|
||||||
framer-motion@12.39.0:
|
|
||||||
resolution: {integrity: sha512-+vnLfzrv0MzjLzNl+nvNvR7jdg3q4cxxjz/YvzfifHl0TREtL00cs1RoMTxs+1PzLiEqZGV6gYsBY0oEAYZ24w==}
|
|
||||||
peerDependencies:
|
|
||||||
'@emotion/is-prop-valid': '*'
|
|
||||||
react: ^18.0.0 || ^19.0.0
|
|
||||||
react-dom: ^18.0.0 || ^19.0.0
|
|
||||||
peerDependenciesMeta:
|
|
||||||
'@emotion/is-prop-valid':
|
|
||||||
optional: true
|
|
||||||
react:
|
|
||||||
optional: true
|
|
||||||
react-dom:
|
|
||||||
optional: true
|
|
||||||
|
|
||||||
fresh@2.0.0:
|
fresh@2.0.0:
|
||||||
resolution: {integrity: sha512-Rx/WycZ60HOaqLKAi6cHRKKI7zxWbJ31MhntmtwMoaTeF7XFH9hhBp8vITaMidfljRQ6eYWCKkaTK+ykVJHP2A==}
|
resolution: {integrity: sha512-Rx/WycZ60HOaqLKAi6cHRKKI7zxWbJ31MhntmtwMoaTeF7XFH9hhBp8vITaMidfljRQ6eYWCKkaTK+ykVJHP2A==}
|
||||||
engines: {node: '>= 0.8'}
|
engines: {node: '>= 0.8'}
|
||||||
@@ -625,9 +589,6 @@ packages:
|
|||||||
resolution: {integrity: sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==}
|
resolution: {integrity: sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==}
|
||||||
engines: {node: '>= 0.4'}
|
engines: {node: '>= 0.4'}
|
||||||
|
|
||||||
gsap@3.15.0:
|
|
||||||
resolution: {integrity: sha512-dMW4CWBTUK1AEEDeZc1g4xpPGIrSf9fJF960qbTZmN/QwZIWY5wgliS6JWl9/25fpTGJrMRtSjGtOmPnfjZB+A==}
|
|
||||||
|
|
||||||
has-symbols@1.1.0:
|
has-symbols@1.1.0:
|
||||||
resolution: {integrity: sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==}
|
resolution: {integrity: sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==}
|
||||||
engines: {node: '>= 0.4'}
|
engines: {node: '>= 0.4'}
|
||||||
@@ -636,9 +597,6 @@ packages:
|
|||||||
resolution: {integrity: sha512-ej4AhfhfL2Q2zpMmLo7U1Uv9+PyhIZpgQLGT1F9miIGmiCJIoCgSmczFdrc97mWT4kVY72KA+WnnhJ5pghSvSg==}
|
resolution: {integrity: sha512-ej4AhfhfL2Q2zpMmLo7U1Uv9+PyhIZpgQLGT1F9miIGmiCJIoCgSmczFdrc97mWT4kVY72KA+WnnhJ5pghSvSg==}
|
||||||
engines: {node: '>= 0.4'}
|
engines: {node: '>= 0.4'}
|
||||||
|
|
||||||
hey-listen@1.0.8:
|
|
||||||
resolution: {integrity: sha512-COpmrF2NOg4TBWUJ5UVyaCU2A88wEMkUPK4hNqyCkqHbxT92BbvfjoSozkAIIm6XhicGlJHhFdullInrdhwU8Q==}
|
|
||||||
|
|
||||||
hookable@5.5.3:
|
hookable@5.5.3:
|
||||||
resolution: {integrity: sha512-Yc+BQe8SvoXH1643Qez1zqLRmbA5rCL+sSmk6TVos0LWVfNIB7PGncdlId77WzLGSIB5KaWgTaNTs2lNVEI6VQ==}
|
resolution: {integrity: sha512-Yc+BQe8SvoXH1643Qez1zqLRmbA5rCL+sSmk6TVos0LWVfNIB7PGncdlId77WzLGSIB5KaWgTaNTs2lNVEI6VQ==}
|
||||||
|
|
||||||
@@ -875,18 +833,6 @@ packages:
|
|||||||
mlly@1.8.2:
|
mlly@1.8.2:
|
||||||
resolution: {integrity: sha512-d+ObxMQFmbt10sretNDytwt85VrbkhhUA/JBGm1MPaWJ65Cl4wOgLaB1NYvJSZ0Ef03MMEU/0xpPMXUIQ29UfA==}
|
resolution: {integrity: sha512-d+ObxMQFmbt10sretNDytwt85VrbkhhUA/JBGm1MPaWJ65Cl4wOgLaB1NYvJSZ0Ef03MMEU/0xpPMXUIQ29UfA==}
|
||||||
|
|
||||||
motion-dom@12.39.0:
|
|
||||||
resolution: {integrity: sha512-Xn7aAcGDhco/JZTXOub64UmaYn73C6J1Po7Fk+8EvkJsNGTqfhon6UJY53vJKXW5v5Zl8HrYsVxv6oPXeGoGLQ==}
|
|
||||||
|
|
||||||
motion-utils@12.39.0:
|
|
||||||
resolution: {integrity: sha512-8nadJAJjTtqRkmRF36FoJTrywK9nnFmnPwnSMyxaOCU7GDjN9RTMJIxx9De8ErM+vpPhMccr/6fo5WciyQLnMQ==}
|
|
||||||
|
|
||||||
motion-v@1.10.3:
|
|
||||||
resolution: {integrity: sha512-9Ewo/wwGv7FO3PqYJpllBF/Efc7tbeM1iinVrM73s0RUQrnXHwMZCaRX98u4lu0PQCrZghPPfCsQ14pWKIEbnQ==}
|
|
||||||
peerDependencies:
|
|
||||||
'@vueuse/core': '>=10.0.0'
|
|
||||||
vue: '>=3.0.0'
|
|
||||||
|
|
||||||
ms@2.1.3:
|
ms@2.1.3:
|
||||||
resolution: {integrity: sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==}
|
resolution: {integrity: sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==}
|
||||||
|
|
||||||
@@ -1452,8 +1398,6 @@ snapshots:
|
|||||||
dependencies:
|
dependencies:
|
||||||
undici-types: 7.21.0
|
undici-types: 7.21.0
|
||||||
|
|
||||||
'@types/web-bluetooth@0.0.21': {}
|
|
||||||
|
|
||||||
'@vitejs/plugin-vue@6.0.6(vite@8.0.12(@types/node@25.7.0)(jiti@1.21.7)(yaml@2.9.0))(vue@3.5.34)':
|
'@vitejs/plugin-vue@6.0.6(vite@8.0.12(@types/node@25.7.0)(jiti@1.21.7)(yaml@2.9.0))(vue@3.5.34)':
|
||||||
dependencies:
|
dependencies:
|
||||||
'@rolldown/pluginutils': 1.0.0-rc.13
|
'@rolldown/pluginutils': 1.0.0-rc.13
|
||||||
@@ -1537,19 +1481,6 @@ snapshots:
|
|||||||
|
|
||||||
'@vue/shared@3.5.34': {}
|
'@vue/shared@3.5.34': {}
|
||||||
|
|
||||||
'@vueuse/core@14.3.0(vue@3.5.34)':
|
|
||||||
dependencies:
|
|
||||||
'@types/web-bluetooth': 0.0.21
|
|
||||||
'@vueuse/metadata': 14.3.0
|
|
||||||
'@vueuse/shared': 14.3.0(vue@3.5.34)
|
|
||||||
vue: 3.5.34
|
|
||||||
|
|
||||||
'@vueuse/metadata@14.3.0': {}
|
|
||||||
|
|
||||||
'@vueuse/shared@14.3.0(vue@3.5.34)':
|
|
||||||
dependencies:
|
|
||||||
vue: 3.5.34
|
|
||||||
|
|
||||||
accepts@2.0.0:
|
accepts@2.0.0:
|
||||||
dependencies:
|
dependencies:
|
||||||
mime-types: 3.0.2
|
mime-types: 3.0.2
|
||||||
@@ -1813,12 +1744,6 @@ snapshots:
|
|||||||
|
|
||||||
fraction.js@5.3.4: {}
|
fraction.js@5.3.4: {}
|
||||||
|
|
||||||
framer-motion@12.39.0:
|
|
||||||
dependencies:
|
|
||||||
motion-dom: 12.39.0
|
|
||||||
motion-utils: 12.39.0
|
|
||||||
tslib: 2.8.1
|
|
||||||
|
|
||||||
fresh@2.0.0: {}
|
fresh@2.0.0: {}
|
||||||
|
|
||||||
fsevents@2.3.3:
|
fsevents@2.3.3:
|
||||||
@@ -1858,16 +1783,12 @@ snapshots:
|
|||||||
|
|
||||||
gopd@1.2.0: {}
|
gopd@1.2.0: {}
|
||||||
|
|
||||||
gsap@3.15.0: {}
|
|
||||||
|
|
||||||
has-symbols@1.1.0: {}
|
has-symbols@1.1.0: {}
|
||||||
|
|
||||||
hasown@2.0.3:
|
hasown@2.0.3:
|
||||||
dependencies:
|
dependencies:
|
||||||
function-bind: 1.1.2
|
function-bind: 1.1.2
|
||||||
|
|
||||||
hey-listen@1.0.8: {}
|
|
||||||
|
|
||||||
hookable@5.5.3: {}
|
hookable@5.5.3: {}
|
||||||
|
|
||||||
http-errors@2.0.1:
|
http-errors@2.0.1:
|
||||||
@@ -2059,24 +1980,6 @@ snapshots:
|
|||||||
pkg-types: 1.3.1
|
pkg-types: 1.3.1
|
||||||
ufo: 1.6.4
|
ufo: 1.6.4
|
||||||
|
|
||||||
motion-dom@12.39.0:
|
|
||||||
dependencies:
|
|
||||||
motion-utils: 12.39.0
|
|
||||||
|
|
||||||
motion-utils@12.39.0: {}
|
|
||||||
|
|
||||||
motion-v@1.10.3(@vueuse/core@14.3.0(vue@3.5.34))(vue@3.5.34):
|
|
||||||
dependencies:
|
|
||||||
'@vueuse/core': 14.3.0(vue@3.5.34)
|
|
||||||
framer-motion: 12.39.0
|
|
||||||
hey-listen: 1.0.8
|
|
||||||
motion-dom: 12.39.0
|
|
||||||
vue: 3.5.34
|
|
||||||
transitivePeerDependencies:
|
|
||||||
- '@emotion/is-prop-valid'
|
|
||||||
- react
|
|
||||||
- react-dom
|
|
||||||
|
|
||||||
ms@2.1.3: {}
|
ms@2.1.3: {}
|
||||||
|
|
||||||
muggle-string@0.4.1: {}
|
muggle-string@0.4.1: {}
|
||||||
@@ -2420,7 +2323,8 @@ snapshots:
|
|||||||
|
|
||||||
ts-interface-checker@0.1.13: {}
|
ts-interface-checker@0.1.13: {}
|
||||||
|
|
||||||
tslib@2.8.1: {}
|
tslib@2.8.1:
|
||||||
|
optional: true
|
||||||
|
|
||||||
type-is@1.6.18:
|
type-is@1.6.18:
|
||||||
dependencies:
|
dependencies:
|
||||||
|
|||||||
Binary file not shown.
|
Before Width: | Height: | Size: 1.9 MiB |
@@ -3,8 +3,4 @@ import App from "./App.vue"
|
|||||||
import router from "./router"
|
import router from "./router"
|
||||||
import "./assets/main.css"
|
import "./assets/main.css"
|
||||||
|
|
||||||
if (localStorage.getItem("secondtech_theme") === "dark") {
|
|
||||||
document.documentElement.classList.add("dark")
|
|
||||||
}
|
|
||||||
|
|
||||||
createApp(App).use(router).mount("#app")
|
createApp(App).use(router).mount("#app")
|
||||||
|
|||||||
+32
-41
@@ -2,32 +2,15 @@
|
|||||||
<section class="bg-gradient-to-br from-brand-50 via-white to-slate-100">
|
<section class="bg-gradient-to-br from-brand-50 via-white to-slate-100">
|
||||||
<div class="container-page grid min-h-[560px] items-center gap-10 py-16 lg:grid-cols-2">
|
<div class="container-page grid min-h-[560px] items-center gap-10 py-16 lg:grid-cols-2">
|
||||||
<div>
|
<div>
|
||||||
<ShinyText
|
<span class="rounded-full bg-brand-100 px-4 py-2 text-sm font-bold text-brand-700">
|
||||||
text="SecondTech"
|
Marketplace Barang Bekas Teknologi
|
||||||
className="text-1xl font-extrabold tracking-tight sm:text-1xl lg:text-2xl"
|
</span>
|
||||||
:color="'#1c4ed9'"
|
<h1 class="mt-6 text-4xl font-extrabold tracking-tight text-slate-950 sm:text-5xl lg:text-6xl">
|
||||||
:shineColor="'#e0e7ff'"
|
Jual beli perangkat Teknologi bekas.
|
||||||
:spread="45"
|
</h1>
|
||||||
:speed="1"
|
<p class="mt-6 max-w-xl text-lg leading-8 text-slate-600">
|
||||||
:delay="2"
|
Temukan laptop bekas, PC, monitor, keyboard, router, switch, access point, dan server bekas untuk berbagai kebutuhan.
|
||||||
:direction="'left'"
|
</p>
|
||||||
:pauseOnHover="true"
|
|
||||||
:yoyo="true"
|
|
||||||
/>
|
|
||||||
<div>
|
|
||||||
<TextType
|
|
||||||
as="h2"
|
|
||||||
className="mt-6 text-3xl font-extrabold tracking-tight text-zinc-950 sm:text-6xl lg:text-5xl"
|
|
||||||
:text="['Menjual Barang Bekas, yang kamu butuhkan, di satu tempat.']"
|
|
||||||
:typingSpeed="75"
|
|
||||||
:pauseDuration="1500"
|
|
||||||
:showCursor="false"
|
|
||||||
:textColors="['#0f1729']"
|
|
||||||
cursorCharacter="|"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<p class="mt-6 max-w-xl text-lg leading-8 text-slate-600">Temukan laptop bekas, PC, monitor, keyboard, router, switch, access point, dan server bekas untuk berbagai kebutuhan.</p>
|
|
||||||
<div class="mt-8 flex flex-wrap gap-3">
|
<div class="mt-8 flex flex-wrap gap-3">
|
||||||
<RouterLink to="/jual" class="btn-primary">Jual Barang Sekarang</RouterLink>
|
<RouterLink to="/jual" class="btn-primary">Jual Barang Sekarang</RouterLink>
|
||||||
<RouterLink to="/marketplace" class="btn-secondary">Lihat Marketplace</RouterLink>
|
<RouterLink to="/marketplace" class="btn-secondary">Lihat Marketplace</RouterLink>
|
||||||
@@ -36,7 +19,11 @@
|
|||||||
|
|
||||||
<div class="relative">
|
<div class="relative">
|
||||||
<div class="absolute -inset-4 rounded-[2rem] bg-brand-100 blur-2xl"></div>
|
<div class="absolute -inset-4 rounded-[2rem] bg-brand-100 blur-2xl"></div>
|
||||||
<img class="relative aspect-[4/3] w-full rounded-[2rem] object-cover shadow-2xl" src="/kolase home.png" alt="Tech marketplace" />
|
<img
|
||||||
|
class="relative aspect-[4/3] w-full rounded-[2rem] object-cover shadow-2xl"
|
||||||
|
src="https://images.unsplash.com/photo-1516321318423-f06f85e504b3?q=80&w=1200&auto=format&fit=crop"
|
||||||
|
alt="Tech marketplace"
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
@@ -70,31 +57,35 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="grid gap-6 sm:grid-cols-2 lg:grid-cols-4">
|
<div class="grid gap-6 sm:grid-cols-2 lg:grid-cols-4">
|
||||||
<ProductCard v-for="product in latestProducts" :key="product.id" :product="product" :saved="saved.includes(product.id)" @toggle-save="toggleSave" />
|
<ProductCard
|
||||||
|
v-for="product in latestProducts"
|
||||||
|
:key="product.id"
|
||||||
|
:product="product"
|
||||||
|
:saved="saved.includes(product.id)"
|
||||||
|
@toggle-save="toggleSave"
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup>
|
<script setup>
|
||||||
import { computed, ref } from 'vue';
|
import { computed, ref } from "vue"
|
||||||
import { Cpu } from 'lucide-vue-next';
|
import { Cpu } from "lucide-vue-next"
|
||||||
import ProductCard from '../components/ProductCard.vue';
|
import ProductCard from "../components/ProductCard.vue"
|
||||||
import { products, categories } from '../data/products';
|
import { products, categories } from "../data/products"
|
||||||
import { getSavedProducts, setSavedProducts } from '../utils';
|
import { getSavedProducts, setSavedProducts } from "../utils"
|
||||||
import ShinyText from '../vuebits/ShinyText/ShinyText.vue';
|
|
||||||
import TextType from '../vuebits/TextType/TextType.vue';
|
|
||||||
|
|
||||||
const saved = ref(getSavedProducts());
|
const saved = ref(getSavedProducts())
|
||||||
const categoryList = categories.filter((c) => c !== 'Semua').slice(0, 6);
|
const categoryList = categories.filter((c) => c !== "Semua").slice(0, 6)
|
||||||
const latestProducts = computed(() => products.slice(0, 4));
|
const latestProducts = computed(() => products.slice(0, 4))
|
||||||
|
|
||||||
function toggleSave(id) {
|
function toggleSave(id) {
|
||||||
if (saved.value.includes(id)) {
|
if (saved.value.includes(id)) {
|
||||||
saved.value = saved.value.filter((item) => item !== id);
|
saved.value = saved.value.filter((item) => item !== id)
|
||||||
} else {
|
} else {
|
||||||
saved.value.push(id);
|
saved.value.push(id)
|
||||||
}
|
}
|
||||||
setSavedProducts(saved.value);
|
setSavedProducts(saved.value)
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|||||||
@@ -1,8 +1,8 @@
|
|||||||
<template>
|
<template>
|
||||||
<section class="container-page grid min-h-[70vh] place-items-center py-10">
|
<section class="container-page grid min-h-[70vh] place-items-center py-10">
|
||||||
<div class="card w-full max-w-md p-6">
|
<div class="card w-full max-w-md p-6">
|
||||||
<h1 class="text-2xl font-extrabold">Login</h1>
|
<h1 class="text-2xl font-extrabold">Selamat Datang Kembali!</h1>
|
||||||
<p class="mt-2 text-sm text-slate-600">Masuk untuk mengelola barang jualanmu.</p>
|
<p class="mt-2 text-sm text-slate-600">Masuk dan lanjutkan jualanmu.</p>
|
||||||
|
|
||||||
<form class="mt-6 grid gap-4" @submit.prevent="login">
|
<form class="mt-6 grid gap-4" @submit.prevent="login">
|
||||||
<p v-if="errorMessage" class="rounded-xl bg-red-50 px-4 py-3 text-sm font-semibold text-red-700">
|
<p v-if="errorMessage" class="rounded-xl bg-red-50 px-4 py-3 text-sm font-semibold text-red-700">
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
<section class="container-page py-10">
|
<section class="container-page py-10">
|
||||||
<div class="mb-8">
|
<div class="mb-8">
|
||||||
<h1 class="text-3xl font-extrabold">Jual Barang</h1>
|
<h1 class="text-3xl font-extrabold">Jual Barang</h1>
|
||||||
<p class="mt-2 text-slate-600">Posting barang ke marketplace dengan foto asli dari perangkatmu.</p>
|
<p class="mt-2 text-slate-600">Posting barang anda ke marketplace dengan foto produk asli.</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="grid gap-8 lg:grid-cols-[1fr_360px]">
|
<div class="grid gap-8 lg:grid-cols-[1fr_360px]">
|
||||||
@@ -65,7 +65,7 @@
|
|||||||
|
|
||||||
<div>
|
<div>
|
||||||
<label class="label">Deskripsi</label>
|
<label class="label">Deskripsi</label>
|
||||||
<textarea v-model="form.description" class="input mt-2 min-h-32" placeholder="Jelaskan kondisi barang..." required></textarea>
|
<textarea v-model="form.description" class="input mt-2 min-h-32" placeholder="Deskripsikan kondisi barang anda..." required></textarea>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<button class="btn-primary w-full" :disabled="loading">
|
<button class="btn-primary w-full" :disabled="loading">
|
||||||
|
|||||||
@@ -1,135 +0,0 @@
|
|||||||
<script setup lang="ts">
|
|
||||||
import { Motion, useAnimationFrame, useMotionValue, useTransform } from 'motion-v';
|
|
||||||
import { computed, ref, watch } from 'vue';
|
|
||||||
|
|
||||||
interface ShinyTextProps {
|
|
||||||
text: string;
|
|
||||||
disabled?: boolean;
|
|
||||||
speed?: number;
|
|
||||||
className?: string;
|
|
||||||
color?: string;
|
|
||||||
shineColor?: string;
|
|
||||||
spread?: number;
|
|
||||||
yoyo?: boolean;
|
|
||||||
pauseOnHover?: boolean;
|
|
||||||
direction?: 'left' | 'right';
|
|
||||||
delay?: number;
|
|
||||||
}
|
|
||||||
|
|
||||||
const props = withDefaults(defineProps<ShinyTextProps>(), {
|
|
||||||
disabled: false,
|
|
||||||
speed: 2,
|
|
||||||
className: '',
|
|
||||||
color: '#b5b5b5',
|
|
||||||
shineColor: '#ffffff',
|
|
||||||
spread: 120,
|
|
||||||
yoyo: false,
|
|
||||||
pauseOnHover: false,
|
|
||||||
direction: 'left',
|
|
||||||
delay: 0
|
|
||||||
});
|
|
||||||
|
|
||||||
const isPaused = ref(false);
|
|
||||||
const progress = useMotionValue(0);
|
|
||||||
const elapsedRef = ref(0);
|
|
||||||
const lastTimeRef = ref<number | null>(null);
|
|
||||||
const directionRef = ref(props.direction === 'left' ? 1 : -1);
|
|
||||||
|
|
||||||
const animationDuration = computed(() => props.speed * 1000);
|
|
||||||
const delayDuration = computed(() => props.delay * 1000);
|
|
||||||
|
|
||||||
useAnimationFrame(time => {
|
|
||||||
if (props.disabled || isPaused.value) {
|
|
||||||
lastTimeRef.value = null;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (lastTimeRef.value === null) {
|
|
||||||
lastTimeRef.value = time;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const deltaTime = time - lastTimeRef.value;
|
|
||||||
lastTimeRef.value = time;
|
|
||||||
|
|
||||||
elapsedRef.value += deltaTime;
|
|
||||||
|
|
||||||
// Animation goes from 0 to 100
|
|
||||||
if (props.yoyo) {
|
|
||||||
const cycleDuration = animationDuration.value + delayDuration.value;
|
|
||||||
const fullCycle = cycleDuration * 2;
|
|
||||||
const cycleTime = elapsedRef.value % fullCycle;
|
|
||||||
|
|
||||||
if (cycleTime < animationDuration.value) {
|
|
||||||
// Forward animation: 0 -> 100
|
|
||||||
const p = (cycleTime / animationDuration.value) * 100;
|
|
||||||
progress.set(directionRef.value === 1 ? p : 100 - p);
|
|
||||||
} else if (cycleTime < cycleDuration) {
|
|
||||||
// Delay at end
|
|
||||||
progress.set(directionRef.value === 1 ? 100 : 0);
|
|
||||||
} else if (cycleTime < cycleDuration + animationDuration.value) {
|
|
||||||
// Reverse animation: 100 -> 0
|
|
||||||
const reverseTime = cycleTime - cycleDuration;
|
|
||||||
const p = 100 - (reverseTime / animationDuration.value) * 100;
|
|
||||||
progress.set(directionRef.value === 1 ? p : 100 - p);
|
|
||||||
} else {
|
|
||||||
// Delay at start
|
|
||||||
progress.set(directionRef.value === 1 ? 0 : 100);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
const cycleDuration = animationDuration.value + delayDuration.value;
|
|
||||||
const cycleTime = elapsedRef.value % cycleDuration;
|
|
||||||
|
|
||||||
if (cycleTime < animationDuration.value) {
|
|
||||||
// Animation phase: 0 -> 100
|
|
||||||
const p = (cycleTime / animationDuration.value) * 100;
|
|
||||||
progress.set(directionRef.value === 1 ? p : 100 - p);
|
|
||||||
} else {
|
|
||||||
// Delay phase - hold at end (shine off-screen)
|
|
||||||
progress.set(directionRef.value === 1 ? 100 : 0);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
watch(
|
|
||||||
() => props.direction,
|
|
||||||
() => {
|
|
||||||
directionRef.value = props.direction === 'left' ? 1 : -1;
|
|
||||||
elapsedRef.value = 0;
|
|
||||||
progress.set(0);
|
|
||||||
},
|
|
||||||
{
|
|
||||||
immediate: true
|
|
||||||
}
|
|
||||||
);
|
|
||||||
|
|
||||||
const backgroundPosition = useTransform(progress, p => `${150 - p * 2}% center`);
|
|
||||||
|
|
||||||
const handleMouseEnter = () => {
|
|
||||||
if (props.pauseOnHover) isPaused.value = true;
|
|
||||||
};
|
|
||||||
|
|
||||||
const handleMouseLeave = () => {
|
|
||||||
if (props.pauseOnHover) isPaused.value = false;
|
|
||||||
};
|
|
||||||
|
|
||||||
const gradientStyle = computed(() => ({
|
|
||||||
backgroundImage: `linear-gradient(${props.spread}deg, ${props.color} 0%, ${props.color} 35%, ${props.shineColor} 50%, ${props.color} 65%, ${props.color} 100%)`,
|
|
||||||
backgroundSize: '200% auto',
|
|
||||||
WebkitBackgroundClip: 'text',
|
|
||||||
backgroundClip: 'text',
|
|
||||||
WebkitTextFillColor: 'transparent'
|
|
||||||
}));
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<template>
|
|
||||||
<Motion
|
|
||||||
tag="span"
|
|
||||||
:class="['inline-block', className]"
|
|
||||||
:style="{ ...gradientStyle, backgroundPosition }"
|
|
||||||
@mouseenter="handleMouseEnter"
|
|
||||||
@mouseleave="handleMouseLeave"
|
|
||||||
>
|
|
||||||
{{ text }}
|
|
||||||
</Motion>
|
|
||||||
</template>
|
|
||||||
@@ -1,176 +0,0 @@
|
|||||||
<script setup lang="ts">
|
|
||||||
import { ref, onMounted, onBeforeUnmount, watch, computed, useTemplateRef } from 'vue';
|
|
||||||
import { gsap } from 'gsap';
|
|
||||||
|
|
||||||
interface TextTypeProps {
|
|
||||||
className?: string;
|
|
||||||
showCursor?: boolean;
|
|
||||||
hideCursorWhileTyping?: boolean;
|
|
||||||
cursorCharacter?: string;
|
|
||||||
cursorBlinkDuration?: number;
|
|
||||||
cursorClassName?: string;
|
|
||||||
text: string | string[];
|
|
||||||
as?: string;
|
|
||||||
typingSpeed?: number;
|
|
||||||
initialDelay?: number;
|
|
||||||
pauseDuration?: number;
|
|
||||||
deletingSpeed?: number;
|
|
||||||
loop?: boolean;
|
|
||||||
textColors?: string[];
|
|
||||||
variableSpeed?: { min: number; max: number };
|
|
||||||
onSentenceComplete?: (sentence: string, index: number) => void;
|
|
||||||
startOnVisible?: boolean;
|
|
||||||
reverseMode?: boolean;
|
|
||||||
}
|
|
||||||
|
|
||||||
const props = withDefaults(defineProps<TextTypeProps>(), {
|
|
||||||
as: 'div',
|
|
||||||
typingSpeed: 50,
|
|
||||||
initialDelay: 0,
|
|
||||||
pauseDuration: 2000,
|
|
||||||
deletingSpeed: 30,
|
|
||||||
loop: true,
|
|
||||||
className: '',
|
|
||||||
showCursor: true,
|
|
||||||
hideCursorWhileTyping: false,
|
|
||||||
cursorCharacter: '|',
|
|
||||||
cursorBlinkDuration: 0.5,
|
|
||||||
textColors: () => [],
|
|
||||||
startOnVisible: false,
|
|
||||||
reverseMode: false
|
|
||||||
});
|
|
||||||
|
|
||||||
const displayedText = ref('');
|
|
||||||
const currentCharIndex = ref(0);
|
|
||||||
const isDeleting = ref(false);
|
|
||||||
const currentTextIndex = ref(0);
|
|
||||||
const isVisible = ref(!props.startOnVisible);
|
|
||||||
const cursorRef = useTemplateRef('cursorRef');
|
|
||||||
const containerRef = useTemplateRef('containerRef');
|
|
||||||
|
|
||||||
const textArray = computed(() => (Array.isArray(props.text) ? props.text : [props.text]));
|
|
||||||
|
|
||||||
const getRandomSpeed = () => {
|
|
||||||
if (!props.variableSpeed) return props.typingSpeed;
|
|
||||||
const { min, max } = props.variableSpeed;
|
|
||||||
return Math.random() * (max - min) + min;
|
|
||||||
};
|
|
||||||
|
|
||||||
const getCurrentTextColor = () => {
|
|
||||||
if (!props.textColors.length) return '#ffffff';
|
|
||||||
return props.textColors[currentTextIndex.value % props.textColors.length];
|
|
||||||
};
|
|
||||||
|
|
||||||
let timeout: ReturnType<typeof setTimeout> | null = null;
|
|
||||||
|
|
||||||
const clearTimeoutIfNeeded = () => {
|
|
||||||
if (timeout) clearTimeout(timeout);
|
|
||||||
};
|
|
||||||
|
|
||||||
const executeTypingAnimation = () => {
|
|
||||||
const currentText = textArray.value[currentTextIndex.value];
|
|
||||||
const processedText = props.reverseMode ? currentText.split('').reverse().join('') : currentText;
|
|
||||||
|
|
||||||
if (isDeleting.value) {
|
|
||||||
if (displayedText.value === '') {
|
|
||||||
isDeleting.value = false;
|
|
||||||
if (currentTextIndex.value === textArray.value.length - 1 && !props.loop) return;
|
|
||||||
|
|
||||||
props.onSentenceComplete?.(textArray.value[currentTextIndex.value], currentTextIndex.value);
|
|
||||||
|
|
||||||
currentTextIndex.value = (currentTextIndex.value + 1) % textArray.value.length;
|
|
||||||
currentCharIndex.value = 0;
|
|
||||||
timeout = setTimeout(() => {}, props.pauseDuration);
|
|
||||||
} else {
|
|
||||||
timeout = setTimeout(() => {
|
|
||||||
displayedText.value = displayedText.value.slice(0, -1);
|
|
||||||
}, props.deletingSpeed);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
if (currentCharIndex.value < processedText.length) {
|
|
||||||
timeout = setTimeout(
|
|
||||||
() => {
|
|
||||||
displayedText.value += processedText[currentCharIndex.value];
|
|
||||||
currentCharIndex.value += 1;
|
|
||||||
},
|
|
||||||
props.variableSpeed ? getRandomSpeed() : props.typingSpeed
|
|
||||||
);
|
|
||||||
} else if (textArray.value.length > 1) {
|
|
||||||
timeout = setTimeout(() => {
|
|
||||||
isDeleting.value = true;
|
|
||||||
}, props.pauseDuration);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
watch(
|
|
||||||
[displayedText, currentCharIndex, isDeleting, isVisible],
|
|
||||||
() => {
|
|
||||||
if (!isVisible.value) return;
|
|
||||||
clearTimeoutIfNeeded();
|
|
||||||
|
|
||||||
if (currentCharIndex.value === 0 && !isDeleting.value && displayedText.value === '') {
|
|
||||||
timeout = setTimeout(() => {
|
|
||||||
executeTypingAnimation();
|
|
||||||
}, props.initialDelay);
|
|
||||||
} else {
|
|
||||||
executeTypingAnimation();
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{ immediate: true }
|
|
||||||
);
|
|
||||||
|
|
||||||
onMounted(() => {
|
|
||||||
if (props.showCursor && cursorRef.value) {
|
|
||||||
gsap.set(cursorRef.value, { opacity: 1 });
|
|
||||||
gsap.to(cursorRef.value, {
|
|
||||||
opacity: 0,
|
|
||||||
duration: props.cursorBlinkDuration,
|
|
||||||
repeat: -1,
|
|
||||||
yoyo: true,
|
|
||||||
ease: 'power2.inOut'
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
if (props.startOnVisible && containerRef.value) {
|
|
||||||
const observer = new IntersectionObserver(
|
|
||||||
entries => {
|
|
||||||
entries.forEach(entry => {
|
|
||||||
if (entry.isIntersecting) isVisible.value = true;
|
|
||||||
});
|
|
||||||
},
|
|
||||||
{ threshold: 0.1 }
|
|
||||||
);
|
|
||||||
if (containerRef.value instanceof Element) {
|
|
||||||
observer.observe(containerRef.value);
|
|
||||||
}
|
|
||||||
onBeforeUnmount(() => observer.disconnect());
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
onBeforeUnmount(() => {
|
|
||||||
clearTimeoutIfNeeded();
|
|
||||||
});
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<template>
|
|
||||||
<component
|
|
||||||
:is="as"
|
|
||||||
ref="containerRef"
|
|
||||||
:class="`inline-block whitespace-pre-wrap tracking-tight ${className}`"
|
|
||||||
v-bind="$attrs"
|
|
||||||
>
|
|
||||||
<span class="inline" :style="{ color: getCurrentTextColor() }">
|
|
||||||
{{ displayedText }}
|
|
||||||
</span>
|
|
||||||
<span
|
|
||||||
v-if="showCursor"
|
|
||||||
ref="cursorRef"
|
|
||||||
:class="`ml-1 inline-block opacity-100 ${
|
|
||||||
hideCursorWhileTyping && (currentCharIndex < textArray[currentTextIndex].length || isDeleting) ? 'hidden' : ''
|
|
||||||
} ${cursorClassName}`"
|
|
||||||
>
|
|
||||||
{{ cursorCharacter }}
|
|
||||||
</span>
|
|
||||||
</component>
|
|
||||||
</template>
|
|
||||||
Reference in New Issue
Block a user