Files
atahan/app/pages/index.vue
Beyhan Oğur 763b147cc3 first commit
2026-04-26 22:04:35 +03:00

802 lines
30 KiB
Vue
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<script setup lang="ts">
import BackToTopStart from "~/components/home/BackToTopStart.vue";
import MainMenuArea from "~/components/home/MainMenuArea.vue";
import type {
Home,
Setting,
AboutMe,
Service,
ServiceTitle,
Resume,
Category,
MainMenu,
OpenClose,
Bundle
} from "~/types";
import Preloader from "~/components/Preloader.vue";
const token = ref('')
const config = useRuntimeConfig()
const apiUrl = computed(() => config.public.BASE_API_URL || 'http://127.0.0.1:8000')
const {data: openClose} = await useFetch<OpenClose>(`${apiUrl.value}/api/v1/open-close/`)
const {data: bundle} = await useFetch<Bundle>(`${apiUrl.value}/api/v1/bundle/`)
const {data: categories} = await useFetch<Category[]>(`${apiUrl.value}/api/v1/categories/`)
const home = computed(() => bundle.value?.home)
const aboutme = computed(() => bundle.value?.about_me)
const services_title = computed(() => bundle.value?.service_title)
const resume = computed(() => bundle.value?.resume)
const menu = computed(() => bundle.value?.main_menu)
const services = computed(() => bundle.value?.services)
// Setting and Categories are not present in the bundle JSON provided.
// We initialize them as undefined or empty to prevent errors in template.
const setting = computed(() => undefined)
//const categories = computed(() => categories?.value)
// make a typed, safe array for template iteration
const servicesArray = computed(() => (services.value as Service[] | undefined) ?? [])
const categoriesArray = computed(() => (categories.value as Category[] | undefined) ?? [])
// Helper function to get full image URL
const getImageUrl = (imagePath: string | null | undefined) => {
if (!imagePath) return '/assets/images/work/placeholder.jpg'
if (imagePath.startsWith('http')) return imagePath
return `${apiUrl.value}${imagePath}`
}
const selectedCategoryId = ref<number | 'all'>('all')
const normalizeCategoryIds = (
categories?: Array<number | { id: number | null } | null>
) => {
return (categories || [])
.map((entry) => (typeof entry === 'number' ? entry : entry?.id ?? null))
.filter((id): id is number => typeof id === 'number')
}
// Flatten all portfolio items from all categories, deduplicated by id
const portfolioItems = computed(() => {
if (!categories.value) return []
const byId = new Map<number, any>()
for (const category of categories.value) {
for (const portfolio of category.portfolio_categories || []) {
const categoryIds = normalizeCategoryIds(portfolio.categories)
if (category?.id && !categoryIds.includes(category.id)) {
categoryIds.push(category.id)
}
const existing = byId.get(portfolio.id)
if (existing) {
const merged = Array.from(new Set([...existing.categoryIds, ...categoryIds]))
existing.categoryIds = merged
existing.categoryClasses = merged.map((id: number) => `cat-${id}`).join(' ')
continue
}
byId.set(portfolio.id, {
...portfolio,
categoryIds,
categoryClasses: categoryIds.map((id: number) => `cat-${id}`).join(' ')
})
}
}
return Array.from(byId.values())
})
const filteredPortfolioItems = computed(() => {
const selected = selectedCategoryId.value
const items = selected === 'all'
? portfolioItems.value
: portfolioItems.value.filter((item: any) =>
Array.isArray(item.categoryIds) && item.categoryIds.includes(selected)
)
// Anasayfada maksimum 6 portfolio göster
return items.slice(0, 6)
})
/**
* Strip HTML and truncate to `max` characters, returning plain text.
*/
const truncateHtml = (html?: string, max = 120) => {
if (!html) return ''
// remove HTML tags
const text = html.replace(/<\/?[^>]+(>|$)/g, '')
if (text.length <= max) return text
return text.slice(0, max).trim() + '...'
}
// Contact form state
const contactForm = ref({
name: '',
email: '',
subject: '',
message: ''
})
const isSubmitting = ref(false)
const submitMessage = ref('')
const submitError = ref('')
// Contact form submit handler
const handleContactSubmit = async (event: Event) => {
event.preventDefault()
// Reset messages
submitMessage.value = ''
submitError.value = ''
// Validate turnstile token
if (!token.value) {
submitError.value = 'Lütfen captcha doğrulamasını tamamlayın'
return
}
// Validate form fields
if (!contactForm.value.name || !contactForm.value.email ||
!contactForm.value.subject || !contactForm.value.message) {
submitError.value = 'Lütfen tüm alanları doldurun'
return
}
isSubmitting.value = true
try {
const response = await $fetch('/api/contact/post.create', {
method: 'POST',
body: {
name: contactForm.value.name,
email: contactForm.value.email,
subject: contactForm.value.subject,
message: contactForm.value.message
}
})
// @ts-expect-error - API response type
submitMessage.value = response?.message || 'Mesajınız başarıyla gönderildi!'
// Reset form
contactForm.value = {
name: '',
email: '',
subject: '',
message: ''
}
// Reset turnstile
token.value = ''
} catch (error: any) {
console.error('Contact form error:', error)
submitError.value = error?.data?.statusMessage || 'Mesaj gönderilirken bir hata oluştu'
} finally {
isSubmitting.value = false
}
}
// http://212.64.215.243:1337/api/homes?filters[published&populate=*
// http://127.0.0.1:8000/api/v1/services/
</script>
<template>
<div v-if="openClose?.site_active">
<!-- <preloader/>-->
<!--Main-Menu Area Start-->
<main-menu-area :home="home" :setting="setting" :menu="menu"/>
<!--Main-Menu Area Start-->
<!-- Main Content Area Start -->
<div class="main-content">
<div class="main-content-inner">
<!-- About div Start -->
<div class="home-section" id="home">
<ClientOnly>
<div class="particals"></div>
</ClientOnly>
<div class="container">
<div class="row justify-content-center">
<div class="col-lg-6">
<div class="home-content">
<div class="home-image">
<img :src="getImageUrl(home?.image)" :alt="home?.name" class="wow zoomIn" data-wow-delay="0.2s">
</div>
<div class="home-main-content">
<h4 class="heading wow fadeInUp" data-wow-delay="0.3s">
{{ home?.name }}
</h4>
<div class="social-info wow fadeInUp" data-wow-delay="0.5s">
<ul>
<li>
<a :href="setting?.facebook">
<i class="fab fa-facebook-f"></i>
</a>
</li>
<li>
<a :href="setting?.x">
<i class="fab fa-twitter"></i>
</a>
</li>
<li>
<a :href="setting?.linkedin">
<i class="fab fa-linkedin-in"></i>
</a>
</li>
<li>
<a :href="setting?.instagram">
<i class="fab fa-instagram"></i>
</a>
</li>
<li>
<a :href="setting?.pinterest">
<i class="fab fa-pinterest-p"></i>
</a>
</li>
</ul>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
<!-- About div End -->
<!-- About div Start -->
<div v-if="aboutme?.is_active" class="about-section" id="about">
<div class="container">
<div class="row justify-content-center">
<div class="col-lg-12">
<div class="section-heading wow fadeInUp" data-wow-delay="0.2s">
<h2 class="title">
{{ aboutme?.title }} <span class="color"></span>
<span class="bg-text">{{ aboutme?.title }}</span>
</h2>
</div>
</div>
</div>
<div class="row">
<div class="col-lg-12">
<div class="about">
<div class="row">
<div class="col-lg-4">
<div class="about-image wow fadeInUp" data-wow-delay="0.3s">
<img :src="getImageUrl(aboutme?.image)" :alt="aboutme?.title">
</div>
</div>
<div class="col-lg-8 align-self-center">
<div class="short-description wow fadeInUp">
<div v-if="aboutme?.image_sub" v-html="aboutme.image_sub"></div>
<div class="about-links">
<a v-if="aboutme?.cv" :href="aboutme.cv" download class="mybtn3 mybtn-bg">
<span>CV Indir</span> </a>
</div>
</div>
</div>
</div>
<div class="row">
<div class="col-lg-12">
<div class="about-content wow fadeInUp">
<div class="personal-info">
<ul>
<li>
<span><label>Yaş Günü:</label> {{ aboutme?.birthday }}</span>
</li>
<li>
<span><label>Yaşım:</label> {{ aboutme?.age }}</span>
</li>
<li>
<span><label>Şehir:</label> {{ aboutme?.city }}</span>
</li>
<li>
<span><label>İlgi Alanları:</label> {{ aboutme?.interests }}</span>
</li>
<li>
<span><label>Üniversite:</label> {{ aboutme?.study }}</span>
</li>
<li>
<span><label>Derecelendirme:</label> {{ aboutme?.degree }}</span>
</li>
<li>
<span><label>Website:</label> <a :href="aboutme?.website"
target="_blank">{{ aboutme?.website }}</a></span>
</li>
<li>
<span><label>E-Posta:</label> <a :href="`mailto:${aboutme?.mail}`">{{ aboutme?.mail }}</a></span>
</li>
<li>
<span><label>Telefon:</label> <a :href="`tel:${aboutme?.phone}`">{{ aboutme?.phone }}</a></span>
</li>
<li>
<span><label>X:</label> <a :href="aboutme?.x" target="_blank">{{ aboutme?.x }}</a></span>
</li>
</ul>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
<!-- About div End -->
<!-- Counter Area Start -->
<div v-if="aboutme?.counter_active" class="counter-area">
<div class="container">
<div class="row">
<div class="col-lg-3 col-md-6">
<div class="single-counter wow fadeInUp">
<img src="/assets/images/icon/005-target.png" alt="">
<ClientOnly>
<div class="counter-wrapper">
<div class="counter">{{ aboutme?.done || 80 }}</div>
<span>k+</span>
</div>
</ClientOnly>
<p class="text">
{{ aboutme?.project_done || 'Project Done' }}
</p>
</div>
</div>
<div class="col-lg-3 col-md-6">
<div class="single-counter wow fadeInUp">
<img src="/assets/images/icon/002-medical-mask.png" alt="">
<ClientOnly>
<div class="counter-wrapper">
<div class="counter">{{ aboutme?.user_h || 19 }}</div>
<span>k+</span>
</div>
</ClientOnly>
<p class="text">
{{ aboutme?.hapy_user || 'Happy User' }}
</p>
</div>
</div>
<div class="col-lg-3 col-md-6">
<div class="single-counter wow fadeInUp">
<img src="/assets/images/icon/053-success-1.png" alt="">
<ClientOnly>
<div class="counter-wrapper">
<div class="counter">{{ aboutme?.great || 39 }}</div>
<span>k+</span>
</div>
</ClientOnly>
<p class="text">
{{ aboutme?.great_reviews || 'Great Reviews' }}
</p>
</div>
</div>
<div class="col-lg-3 col-md-6">
<div class="single-counter wow fadeInUp">
<img src="/assets/images/icon/045-hacker.png" alt="">
<ClientOnly>
<div class="counter-wrapper">
<div class="counter">{{ aboutme?.team || 25 }}</div>
<span>+</span>
</div>
</ClientOnly>
<p class="text">
{{ aboutme?.support_team || 'Support Team' }}
</p>
</div>
</div>
</div>
</div>
</div>
<!-- Counter Area End -->
<!-- My service Start -->
<div v-if="services_title?.is_active" class="service-wrapper" id="service">
<div class="container">
<div class="row justify-content-center">
<div class="col-lg-12">
<div class="section-heading wow fadeInUp" data-wow-delay="0.2s">
<h2 class="title">
{{ services_title?.title }} <span class="color"></span>
<span class="bg-text">{{ services_title?.title_sub }}</span>
</h2>
</div>
</div>
</div>
<div class="row justify-content-center">
<template v-for="servis in servicesArray" :key="servis.id">
<div class="col-lg-4 col-md-6">
<NuxtLink :to="`/services/${servis.slug}`" class="single-feature wow fadeInUp">
<img :src="getImageUrl(servis?.image)" :alt="servis?.title">
<ClientOnly>
<div class="content">
<h4 class="title">
{{ truncateHtml(servis?.title,21) }}
</h4>
<p>{{ truncateHtml(servis?.content) }}</p>
</div>
</ClientOnly>
</NuxtLink>
</div>
</template>
</div>
</div>
</div>
<!-- My service End -->
<!-- Resume Area Start -->
<div v-if="resume?.is_active" class="resume-wrapper" id="resume">
<div class="container">
<div class="row justify-content-center">
<div class="col-lg-12">
<div class="section-heading wow fadeInUp" data-wow-delay="0.2s">
<h2 class="title">
{{ resume?.title }} <span class="color"></span>
<span class="bg-text">{{ resume?.title_sub }}</span>
</h2>
</div>
</div>
</div>
<div class="row">
<div class="col-lg-6">
<div class="resume-box">
<div class="resume-title">
<h4 class="title">
{{resume?.education}}
</h4>
</div>
<div class="education-list">
<div v-for="edu in resume?.edu_resume" :key="edu.id" class="single-education wow fadeInUp">
<div class="year">
<span>{{ edu.between_years }}</span>
</div>
<h4 class="university-name">
{{ edu.title }}
</h4>
<p class="degree">{{ edu.content }}</p>
</div>
</div>
</div>
</div>
<div class="col-lg-6">
<div class="resume-box">
<div class="resume-title">
<h4 class="title">
{{resume?.experience}}
</h4>
</div>
<div class="education-list">
<div v-for="exp in resume?.exp_resume" :key="exp.id" class="single-education wow fadeInUp">
<div class="year">
<span>{{ exp.between_years }}</span>
</div>
<h4 class="university-name">
{{ exp.title }}
</h4>
<p class="degree">{{ exp.content }}</p>
</div>
</div>
</div>
</div>
<div class="col-lg-6">
<div class="resume-box">
<div class="resume-title">
<h4 class="title">
{{resume?.coding_skills}}
</h4>
</div>
<div class="skill-list">
<div v-for="skill in resume?.skill_resume" :key="skill.id" class="single-skill wow fadeInUp">
<div class="heading">
<h4 class="name">
{{ skill.title }}
</h4>
<h5 class="value">
{{ skill.degree }}%
</h5>
</div>
<div class="progress">
<div class="progress-bar progress-bar-striped progress-bar-animated" role="progressbar"
:aria-valuenow="skill.degree" aria-valuemin="0" aria-valuemax="100"
:style="`width: ${skill.degree}%`"></div>
</div>
</div>
</div>
</div>
</div>
<div class="col-lg-6">
<div class="resume-box">
<div class="resume-title">
<h4 class="title">
{{resume?.knowledge}}
</h4>
</div>
<div class="knowledge-list wow fadeInUp">
<div v-for="know in resume?.know_resume" :key="know.id" class="single-knowledge">
<p>
{{ know.title }}
</p>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
<!-- Resume Area End -->
<!-- My Client Area Area Strat -->
<!-- My Client Area Area End -->
<!-- Portfolio Area Start -->
<div class="project-gallery" id="project-gallery">
<div class="container">
<div class="row justify-content-center">
<div class="col-lg-12">
<div class="section-heading wow fadeInUp" data-wow-delay="0.2s">
<h2 class="title">
<span class="color">Portfolio</span>
<span class="bg-text">Portfolio</span>
</h2>
</div>
</div>
</div>
<div class="row">
<div class="col-lg-12">
<div class="project-gallery-filter d-flex justify-content-center">
<ul class="project-gallery-menu d-inline-block wow fadeInUp" data-wow-delay="0.3s">
<li
:class="['filter', { active: selectedCategoryId === 'all' }]"
data-filter="all"
@click="selectedCategoryId = 'all'"
>
Tümü
</li>
<li
v-for="category in categoriesArray"
:key="category?.id"
:class="['filter', { active: selectedCategoryId === category?.id }]"
:data-filter="`.cat-${category?.id}`"
@click="selectedCategoryId = category?.id"
>
{{ category?.title }}
</li>
</ul>
</div>
<div class="row project-gallery-item">
<div
v-for="portfolio in filteredPortfolioItems"
:key="portfolio.id"
:class="['col-md-6', 'col-lg-4', 'gallery-item', portfolio.categoryClasses]"
>
<div class="gallery-item-content wow fadeInUp">
<div class="item-thumbnail">
<img :src="getImageUrl(portfolio.image)" alt="">
<div class="content-overlay">
<div class="content">
<div class="links">
<a :href="portfolio.url" target="_blank" rel="noopener noreferrer" class="link">
<i class="fas fa-link" />
</a>
<a
class="img-popup image-preview"
:href="getImageUrl(portfolio.image)"
>
<i class="fas fa-eye" />
</a>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
<div class="row">
<div class="col-lg-12 text-center">
<NuxtLink to="/portfolio" class="mybtn3 mybtn-bg wow fadeInUp"><span>Hepsini Gör</span></NuxtLink>
</div>
</div>
</div>
</div>
<!-- Portfolio Area End -->
<!-- Testimonial Start -->
<!-- Testimonial End -->
<!-- Pricing Area Start -->
<!-- Pricing Area End -->
<!-- Blog Area Start -->
<!-- Blog Area End -->
<!-- Contact Area Start -->
<div class="contact" id="contact">
<div class="container">
<div class="row justify-content-center">
<div class="col-lg-12">
<div class="section-heading wow fadeInUp" data-wow-delay="0.2s">
<h2 class="title">
<span class="color">İletişim</span>
<span class="bg-text">İletişim</span>
</h2>
</div>
</div>
</div>
<div class="row justify-content-center">
<div class="col-lg-4 col-md-6">
<!-- Single Info -->
<div class="single-info wow fadeInUp">
<div class="info-icon">
<i class="flaticon-placeholder"></i>
</div>
<div class="info-content">
<h5>Şehir:</h5>
<p>{{ aboutme?.city || '700 Oak Street, Brockton MA 2301' }}</p>
</div>
</div>
</div>
<div class="col-lg-4 col-md-6">
<!-- Single Info -->
<div class="single-info wow fadeInUp">
<div class="info-icon">
<i class="flaticon-telephone"></i>
</div>
<div class="info-content">
<h5>Telefon:</h5>
<p>{{ setting?.phone || '+0123 123 856' }}</p>
</div>
</div>
</div>
<div class="col-lg-4 col-md-6">
<!-- Single Info -->
<div class="single-info wow fadeInUp">
<div class="info-icon">
<i class="flaticon-email-2"></i>
</div>
<div class="info-contentr">
<h5>E-Posta:</h5>
<p>{{ setting?.email || 'demomail@gmail.com' }}</p>
</div>
</div>
</div>
</div>
<div class="row cAndm">
<div class="col-lg-6">
<div class="home-page-form">
<div class="contact-form">
<form id="contact-form" @submit="handleContactSubmit">
<!-- Success/Error Messages -->
<div v-if="submitMessage" class="alert alert-success" role="alert">
{{ submitMessage }}
</div>
<div v-if="submitError" class="alert alert-danger" role="alert">
{{ submitError }}
</div>
<div class="controls">
<div class="row">
<div class="col-md-6">
<div class="form-group">
<input
id="name"
v-model="contactForm.name"
type="text"
name="name"
class="form-control"
placeholder="İsim Soyisim *"
required
:disabled="isSubmitting">
<div class="help-block with-errors"></div>
</div>
</div>
<div class="col-md-6">
<div class="form-group">
<input
id="email"
v-model="contactForm.email"
type="email"
name="email"
class="form-control"
placeholder="E-Posta *"
required
:disabled="isSubmitting">
<div class="help-block with-errors"></div>
</div>
</div>
</div>
<div class="row">
<div class="col-md-12">
<div class="form-group">
<input
id="subject"
v-model="contactForm.subject"
type="text"
name="subject"
class="form-control"
placeholder="Konu *"
required
:disabled="isSubmitting">
<div class="help-block with-errors"></div>
</div>
</div>
</div>
<div class="row">
<div class="col-md-12">
<div class="form-group">
<textarea
id="message"
v-model="contactForm.message"
name="message"
class="form-control"
placeholder="Mesajınız *"
rows="7"
required
:disabled="isSubmitting"></textarea>
<div class="help-block with-errors"></div>
</div>
</div>
<div class="d-flex justify-content-center my-3">
<NuxtTurnstile v-model="token" class="w-100 d-flex justify-content-center" />
</div>
<div class="col-md-12">
<button
type="submit"
class="mybtn3 mybtn-bg"
:disabled="isSubmitting">
<span>{{ isSubmitting ? 'Gönderiliyor...' : 'Gönder' }}</span>
</button>
</div>
</div>
</div>
</form> <!-- End Contact From -->
</div>
</div>
</div>
<div class="col-lg-6">
<div class="google_map_wrapper">
<iframe src="https://www.google.com/maps/embed?pb=!1m18!1m12!1m3!1d48163.351005043995!2d28.738157687720992!3d41.02067366506668!2m3!1f0!2f0!3f0!3m2!1i1024!2i768!4f13.1!3m3!1m2!1s0x14caa4102814e62d%3A0xf17358ce73944614!2zS8O8w6fDvGvDp2VrbWVjZS_EsHN0YW5idWw!5e0!3m2!1str!2str!4v1768471478488!5m2!1str!2str" width="600" height="450" style="border:0;" allowfullscreen="" loading="lazy" referrerpolicy="no-referrer-when-downgrade"></iframe>
</div>
</div>
</div>
<!--/.row-->
</div>
<!--/.container-->
</div>
<!-- Contact Area End -->
</div>
</div>
<!-- Main Content Area End -->
<back-to-top-start/>
</div>
</template>
<style scoped>
</style>