Compare commits

...

2 Commits

15 changed files with 108 additions and 41 deletions

View File

@ -1,23 +1,38 @@
<!DOCTYPE html> <!DOCTYPE html>
<html lang="es"> <html lang="es">
<head> <head>
<meta charset="UTF-8" />
<link href="/avatar-bot.png" rel="icon" type="image/png"/>
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<meta content="Portfolio de Pablo de la Torre" name="description"/>
<meta name="keywords" content="desarrollador, full stack, vue.js, react, node.js, portfolio, programador" />
<meta content="Pablo de la Torre" name="author"/>
<link href="/manifest.json" rel="manifest"/>
<meta content="#3b1070" name="theme-color"/>
<meta content="#3b1070" name="background-color"/>
<meta content="#3b1070" name="apple-mobile-web-app-status-bar-style">
<!-- Open Graph / Facebook -->
<meta property="og:type" content="website" />
<meta content="https://pablotj.com" property="og:url"/>
<meta content="Portfolio | Pablot TJ" property="og:title"/>
<meta content="Portfolio de Pablo de la Torre" property="og:description"/>
<title>Portfolio | Pablot TJ</title> <meta charset="utf-8" />
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<title>Portfolio | Pablo de la Torre Jamardo (Pablo TJ)</title>
<!-- SEO -->
<meta name="description" content="Portfolio personal de Pablo de la Torre Jamardo (Pablo TJ): proyectos, desarrollo software, programación y soluciones tecnológicas." />
<meta name="keywords" content="portfolio, Pablo TJ, Pablo de la Torre, desarrollador, programación, software, tecnología" />
<meta name="robots" content="index, follow" />
<meta name="author" content="Pablo de la Torre Jamardo" />
<link rel="canonical" href="https://pablotj.com/" />
<!-- PWA / Mobile -->
<meta name="theme-color" content="#062342" />
<meta name="apple-mobile-web-app-title" content="Pablo TJ" />
<meta name="apple-mobile-web-app-capable" content="yes" />
<meta name="apple-mobile-web-app-status-bar-style" content="default" />
<!-- Favicon & App Icons -->
<link rel="apple-touch-icon" sizes="180x180" href="/images/apple-touch-icon.png" />
<link rel="manifest" href="/images/site.webmanifest" />
<link rel="shortcut icon" href="/images/favicon.ico" />
<!-- Open Graph (Facebook, LinkedIn, WhatsApp) -->
<meta property="og:title" content="Portfolio | Pablo de la Torre Jamardo (Pablo TJ)" />
<meta property="og:description" content="Explora el portfolio de Pablo TJ: proyectos de software, programación y desarrollo tecnológico." />
<meta property="og:image" content="https://pablotj.com/images/favicon.svg" />
<meta property="og:url" content="https://pablotj.com/" />
<meta property="og:type" content="website" />
</head> </head>
<body> <body>
<div id="app"></div> <div id="app"></div>

10
package-lock.json generated
View File

@ -12,6 +12,7 @@
"vue": "^3.4.21", "vue": "^3.4.21",
"vue-loading-overlay": "^6.0.6", "vue-loading-overlay": "^6.0.6",
"vue-preloader": "^1.1.4", "vue-preloader": "^1.1.4",
"vue-toastification": "^2.0.0-rc.5",
"vue-typer": "^1.2.0", "vue-typer": "^1.2.0",
"vue3-typer": "^1.0.0" "vue3-typer": "^1.0.0"
}, },
@ -2963,6 +2964,15 @@
"node": ">=14" "node": ">=14"
} }
}, },
"node_modules/vue-toastification": {
"version": "2.0.0-rc.5",
"resolved": "https://registry.npmjs.org/vue-toastification/-/vue-toastification-2.0.0-rc.5.tgz",
"integrity": "sha512-q73e5jy6gucEO/U+P48hqX+/qyXDozAGmaGgLFm5tXX4wJBcVsnGp4e/iJqlm9xzHETYOilUuwOUje2Qg1JdwA==",
"license": "MIT",
"peerDependencies": {
"vue": "^3.0.2"
}
},
"node_modules/vue-typer": { "node_modules/vue-typer": {
"version": "1.2.0", "version": "1.2.0",
"resolved": "https://registry.npmjs.org/vue-typer/-/vue-typer-1.2.0.tgz", "resolved": "https://registry.npmjs.org/vue-typer/-/vue-typer-1.2.0.tgz",

View File

@ -30,6 +30,7 @@
"vue": "^3.4.21", "vue": "^3.4.21",
"vue-loading-overlay": "^6.0.6", "vue-loading-overlay": "^6.0.6",
"vue-preloader": "^1.1.4", "vue-preloader": "^1.1.4",
"vue-toastification": "^2.0.0-rc.5",
"vue-typer": "^1.2.0", "vue-typer": "^1.2.0",
"vue3-typer": "^1.0.0" "vue3-typer": "^1.0.0"
}, },

Binary file not shown.

Before

Width:  |  Height:  |  Size: 372 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 69 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 22 KiB

BIN
public/images/favicon.ico Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 496 KiB

View File

@ -0,0 +1,21 @@
{
"name": "Pablo TJ",
"short_name": "Pablo TJ",
"icons": [
{
"src": "/favicon.ico/web-app-manifest-192x192.png",
"sizes": "192x192",
"type": "image/png",
"purpose": "maskable"
},
{
"src": "/favicon.ico/web-app-manifest-512x512.png",
"sizes": "512x512",
"type": "image/png",
"purpose": "maskable"
}
],
"theme_color": "#ffffff",
"background_color": "#ffffff",
"display": "standalone"
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 78 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 476 KiB

View File

@ -3,17 +3,17 @@
"name": "Pablot TJ", "name": "Pablot TJ",
"icons": [ "icons": [
{ {
"src": "avatar-bot.png", "src": "images/favicon-96x96.png",
"sizes": "64x64 32x32 24x24 16x16", "sizes": "64x64 32x32 24x24 16x16",
"type": "image/x-icon" "type": "image/x-icon"
}, },
{ {
"src": "avatar-bot.png", "src": "images/web-app-manifest-192x192.png",
"type": "image/png", "type": "image/png",
"sizes": "192x192" "sizes": "192x192"
}, },
{ {
"src": "avatar-bot.png", "src": "images/web-app-manifest-512x512.png",
"type": "image/png", "type": "image/png",
"sizes": "512x512" "sizes": "512x512"
} }

3
public/robots.txt Normal file
View File

@ -0,0 +1,3 @@
# https://www.robotstxt.org/robotstxt.html
User-agent: *
Disallow:

View File

@ -2,7 +2,8 @@
import {ref} from 'vue' import {ref} from 'vue'
import {Github, Globe, Linkedin, Mail, MapPin, Phone} from 'lucide-vue-next' import {Github, Globe, Linkedin, Mail, MapPin, Phone} from 'lucide-vue-next'
import type {Profile} from '@/domain/models/Profile' import type {Profile} from '@/domain/models/Profile'
import { useToast } from "vue-toastification";
const toast = useToast();
defineProps<{ defineProps<{
profile: Profile profile: Profile
}>() }>()
@ -27,23 +28,37 @@ function getSocialIcon(platform) {
async function handleSubmit() { async function handleSubmit() {
isSubmitting.value = true isSubmitting.value = true
// Simulate form submission try {
await new Promise(resolve => setTimeout(resolve, 1000)) const payload = {
from: form.value.email,
subject: form.value.name,
body: form.value.message
}
// Here you would typically send the form data to your backend const response = await fetch(`${import.meta.env.VITE_MAIL_API_URL}/mail`, {
console.log('Form submitted:', form.value) method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify(payload)
})
// Reset form if (!response.ok) {
form.value = { throw new Error(`Error en el envío: ${response.status}`)
name: '', }
email: '',
message: '' form.value = {
name: '',
email: '',
message: ''
}
toast.success("✅ ¡Mensaje enviado correctamente! Te responderé pronto.")
} catch (error) {
toast.error("❌ Hubo un error al enviar el mensaje. Inténtalo de nuevo.")
} finally {
isSubmitting.value = false
} }
isSubmitting.value = false
// Show success message (you could use a toast notification)
alert('¡Mensaje enviado correctamente! Te responderé pronto.')
} }
</script> </script>
@ -117,15 +132,12 @@ async function handleSubmit() {
<!-- Contact Form --> <!-- Contact Form -->
<div class="bg-gray-50 dark:bg-gray-700 rounded-xl p-8"> <div class="bg-gray-50 dark:bg-gray-700 rounded-xl p-8">
<p class="m-5">¡Hola! Por el momento mi servidor SMTP está de vacaciones 😅.</p>
<p class="m-5">Si quieres contactarme, envíame un correo electrónico directamente y prometo responderte
rápido.</p>
<form @submit.prevent="handleSubmit" class="space-y-6"> <form @submit.prevent="handleSubmit" class="space-y-6">
<div> <div>
<label class="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-2"> <label class="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-2">
Nombre Nombre
</label> </label>
<input disabled <input
v-model="form.name" v-model="form.name"
type="text" type="text"
required required
@ -137,7 +149,7 @@ async function handleSubmit() {
<label class="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-2"> <label class="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-2">
Email Email
</label> </label>
<input disabled <input
v-model="form.email" v-model="form.email"
type="email" type="email"
required required
@ -149,7 +161,7 @@ async function handleSubmit() {
<label class="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-2"> <label class="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-2">
Mensaje Mensaje
</label> </label>
<textarea disabled <textarea
v-model="form.message" v-model="form.message"
rows="4" rows="4"
required required
@ -159,7 +171,7 @@ async function handleSubmit() {
<button <button
type="submit" type="submit"
:disabled="isSubmitting || 1===1" :disabled="isSubmitting"
class="w-full px-6 py-3 bg-purple-600 hover:bg-purple-700 disabled:opacity-50 text-white rounded-lg font-semibold transition-colors" class="w-full px-6 py-3 bg-purple-600 hover:bg-purple-700 disabled:opacity-50 text-white rounded-lg font-semibold transition-colors"
> >
{{ isSubmitting ? 'Enviando...' : 'Enviar Mensaje' }} {{ isSubmitting ? 'Enviando...' : 'Enviar Mensaje' }}

View File

@ -3,10 +3,14 @@ import "./style.css"
import VueTyper from 'vue3-typer' import VueTyper from 'vue3-typer'
import "vue3-typer/dist/vue-typer.css" import "vue3-typer/dist/vue-typer.css"
import App from "./App.vue"; import App from "./App.vue";
import Toast from "vue-toastification";
import "vue-toastification/dist/index.css";
const app = createApp(App) const app = createApp(App)
app.component('VueTyper', VueTyper) app.component('VueTyper', VueTyper)
app.use(Toast, {});
// Global error handler // Global error handler
app.config.errorHandler = (err, vm, info) => { app.config.errorHandler = (err, vm, info) => {
console.error("Global error:", err, info) console.error("Global error:", err, info)