Add 'frontend/' from commit 'c070c0315d66a44154ab7d9f9ea6c211a15f4dba'

git-subtree-dir: frontend
git-subtree-mainline: 205a3bd14b
git-subtree-split: c070c0315d
This commit is contained in:
2025-01-15 15:24:17 -05:00
318 changed files with 29301 additions and 0 deletions

View File

@@ -0,0 +1,78 @@
<template>
<div class="fixed z-50 bottom-6 right-6 flex flex-column">
<div
v-if="showPopup"
ref="popup"
class="z-50 shadow-md shadow-gray-500 rounded-2xl"
>
<div class="bg-fuchsia-900 p-4 rounded-t-2xl font-semibold self-center text-white text-center">
Je Soutiens!
</div>
<div class="bg-gray-100 rounded-b-2xl p-4">
<div class="mx-2">
<StripePayment :creator-id="creatorId"></StripePayment>
</div>
</div>
</div>
<div
@click="togglePopup"
ref="popupButton"
class="bg-purple rounded-full w-16 h-16 flex justify-center items-center self-end mt-4 cursor-pointer"
style="background: radial-gradient(circle, rgba(163,14,121,1) 50%, rgba(107,0,101,1) 100%); border: 2px solid white;"
>
<v-icon class="text-2xl">mdi-gift-outline</v-icon>
</div>
</div>
</template>
<script setup>
import {ref, onMounted, onUnmounted} from 'vue';
import StripePayment from "@/views/StripePayment.vue";
const showPopup = ref(false);
const popup = ref(null);
const popupButton = ref(null);
const props = defineProps({
creatorId: {type: String, required: true},
});
const togglePopup = () => {
showPopup.value = !showPopup.value;
};
const handleClickOutside = (event) => {
if (
popup.value &&
!popup.value.contains(event.target) &&
!popupButton.value.contains(event.target) &&
!event.target.closest('.bg-purple')
) {
showPopup.value = false;
}
};
onMounted(() => {
document.addEventListener('click', handleClickOutside);
});
onUnmounted(() => {
document.removeEventListener('click', handleClickOutside);
});
</script>
<style scoped>
.bg-fuchsia-900 {
background-color: #9c27b0;
}
.bg-gray-100 {
background-color: #f5f5f5;
}
.bg-purple {
background-color: #9c27b0;
}
</style>

View File

@@ -0,0 +1,106 @@
<script setup>
import XIcon from '@/assets/icons/x.svg'
import FacebookIcon from '@/assets/icons/facebook.svg'
import InstagramIcon from '@/assets/icons/instagram.svg'
import {useBrandingStore} from "@/stores/brandingStore.js";
const brandingStore = useBrandingStore()
</script>
<template>
<footer class="py-8 flex flex-col gap-8" :style="{color: brandingStore.colors.onBackground}">
<div class="centered-text text-2xl font-bold flex justify-center items-center ml-28 lg:tracking-[125px] md:tracking-[50px] sm:tracking-[20px]">Hutopy</div>
<div class="flex flex-row justify-center gap-10">
<a href="https://www.facebook.com/profile.php?id=61556819217561">
<facebook-icon class="icon" :style="{ fill: brandingStore.colors.onBackground }" ></facebook-icon>
</a>
<a href="https://www.instagram.com/hutopy.inc/">
<instagram-icon class="icon" :style="{ fill: brandingStore.colors.onBackground }"></instagram-icon>
</a>
<a href="https://x.com/Hutopyinc/">
<x-icon class="icon" :style="{ fill: brandingStore.colors.onBackground }"></x-icon>
</a>
</div>
<div class="flex flex-row flex-wrap justify-center gap-4 px-4 " >
<router-link to="/documents/helpandcontact" :style="{color: brandingStore.colors.onBackground}">
{{ $t('footer.helpandcontact') }}
</router-link>
<router-link to="/documents/faq" :style="{color: brandingStore.colors.onBackground}">
{{ $t('footer.faq') }}
</router-link>
<router-link to="/documents/guideforcreators" :style="{color: brandingStore.colors.onBackground}">
{{ $t('footer.creatorguide') }}
</router-link>
<router-link to="/documents/termsandconditions" :style="{color: brandingStore.colors.onBackground}">
{{ $t('footer.termsandconditions') }}
</router-link>
<router-link to="/documents/contentpolicy" :style="{color: brandingStore.colors.onBackground}">
{{ $t('footer.contentpolicy') }}
</router-link>
<router-link to="/documents/about" :style="{color: brandingStore.colors.onBackground}">
{{ $t('footer.about') }}
</router-link>
<router-link to="/documents/pricing" :style="{color: brandingStore.colors.onBackground}">
{{ $t('footer.pricing') }}
</router-link>
</div>
<div class="flex justify-center base-text mb-13" :style="{color: brandingStore.colors.onBackground}">
Hutopy &copy;{{ new Date().getFullYear() }} - {{ $t('footer.allRightsReserved') }}
</div>
</footer>
</template>
<style scoped>
.icon {
width: 30px;
height: 30px;
}
.base-text {
@apply text-gray-600 tracking-widest font-sans text-sm uppercase
}
a {
@apply base-text
}
a:hover {
@apply text-gray-400
}
.centered-text {
display: flex;
justify-content: center;
align-items: center;
letter-spacing: 125px;
font-size: 2rem;
font-weight: bold;
margin-left: 7rem;
}
@media (max-width: 768px) {
.centered-text {
letter-spacing: 60px;
margin-left: 2rem;
}
}
@media (max-width: 640px) {
.centered-text {
letter-spacing: 40px;
margin-left: 1rem;
}
}
</style>

View File

@@ -0,0 +1,91 @@
<template>
<!-- <div class="bg-gray-100">-->
<!-- <div class="py-6">-->
<!-- <div class=" mx-auto flex justify-center">-->
<!-- <img src="/images/hutopymedia/banners/hutopy.png" alt="Hutopy Logo" class="h-24">-->
<!-- </div>-->
<!-- </div>-->
<!-- </div>-->
<!-- <div class="mx-auto flex justify-center pt-10 max-w-[980px]">-->
<!-- <img src="/images/hutopymedia/homepage/bannierehomepage.png" alt="Create CallToAction"-->
<!-- class="max-w-full block rounded-none md:rounded-2xl">-->
<!-- </div>-->
<!-- <div>-->
<!-- <div-->
<!-- class="mx-auto flex flex-col md:flex-row justify-center max-w-[1000px] space-y-2 md:space-x-4 md:space-y-0 py-5">-->
<!-- <div class="relative group w-full max-w-[250px] md:max-w-[306px] rounded-2xl overflow-hidden mx-auto">-->
<!-- <img src="/images/hutopymedia/homepage/creer.png" alt="Create CallToAction" class="w-full rounded-2xl">-->
<!-- <div-->
<!-- class="absolute inset-0 bg-fuchsia-600 bg-opacity-0 group-hover:bg-opacity-80 flex items-center justify-center transition duration-300">-->
<!-- <p class="text-white text-lg opacity-0 group-hover:opacity-100 transition duration-300 m-3 text-justify">-->
<!-- Libérez votre créativité sur Hutopy, chaque idée trouve sa place et chaque créateur détient la clé d'un-->
<!-- monde rempli de possibilités infinies. Rejoignez-nous et transformez votre passion en réalité.-->
<!-- </p>-->
<!-- </div>-->
<!-- </div>-->
<!-- <div class="relative group w-full max-w-[250px] md:max-w-[306px] rounded-2xl overflow-hidden mx-auto">-->
<!-- <img src="/images/hutopymedia/homepage/partager.png" alt="Share CallToAction" class="w-full rounded-2xl">-->
<!-- <div-->
<!-- class="absolute inset-0 bg-fuchsia-600 bg-opacity-0 group-hover:bg-opacity-80 flex items-center justify-center transition duration-300">-->
<!-- <p class="text-white text-lg opacity-0 group-hover:opacity-100 transition duration-300 m-3 text-justify">-->
<!-- Plongez dans l'univers Hutopy et découvrez un espace profiter rime avec s'enrichir. Savourez des contenus-->
<!-- uniques, des interactions authentiques et une expérience personnalisée conçue pour éveiller vos sens et-->
<!-- enrichir votre quotidien.-->
<!-- </p>-->
<!-- </div>-->
<!-- </div>-->
<!-- <div class="relative group w-full max-w-[250px] md:max-w-[306px] rounded-2xl overflow-hidden mx-auto">-->
<!-- <img src="/images/hutopymedia/homepage/inspirer.png" alt="Inspire CallToAction" class="w-full rounded-2xl">-->
<!-- <div-->
<!-- class="absolute inset-0 bg-fuchsia-600 bg-opacity-0 group-hover:bg-opacity-80 flex items-center justify-center transition duration-300">-->
<!-- <p class="text-white text-lg opacity-0 group-hover:opacity-100 transition duration-300 m-3 text-justify">-->
<!-- Devenez une source d'inspiration sur Hutopy, en partageant votre vision, votre talent et vos histoires.-->
<!-- Influencez positivement la communauté, éveillez la curiosité et inspirez les autres à poursuivre leurs rêves-->
<!-- dans un cercle vertueux de créativité et d'inspiration.-->
<!-- </p>-->
<!-- </div>-->
<!-- </div>-->
<!-- </div>-->
<!-- </div>-->
<div>
<!-- Main Content Section -->
<div class="max-w-4xl mx-auto px-6 py-8 space-y-6">
<img src="/images/hutopymedia/homepage/votrehutopy.png" alt="YourHutopy" class="mx-auto mb-8">
<div>
<div class="space-y-4">
<p class="text-lg leading-relaxed text-justify">
Notre plateforme offre à ses utilisateurs un espace dédié pour centraliser leurs réseaux sociaux sur leur page personnelle. Grâce à cette fonctionnalité, chaque utilisateur peut rediriger son audience vers ses différents comptes et sites web de manière simple et efficace.
De plus, notre site permet aux créateurs de recevoir des donations directement via leur page, leur offrant un moyen supplémentaire de soutien financier.
Ainsi, les utilisateurs peuvent facilement connecter leurs visiteurs à leur univers digital tout en renforçant leur communauté et en augmentant les interactions sur leurs autres plateformes de réseaux sociaux.
</p>
</div>
</div>
</div>
</div>
</template>
<style scoped>
.overlay p {
color: white;
font-size: 1.5rem;
text-align: center;
}
body {
background-color: #F4F4F4;
}
</style>

View File

@@ -0,0 +1,33 @@
<template>
<div class="flex flex-col items-center min-w-[300px] m-4">
<h1 class="text-center text-2xl font-bold mb-5">Connexion</h1>
<google-login class="w-full"
:callback="googleCallback"
popup-type="TOKEN">
<v-btn density="comfortable" class="mb-2 w-full">
<v-icon left>mdi-google</v-icon>
Google
</v-btn>
</google-login>
</div>
</template>
<script setup>
import {ref} from 'vue';
import {useAuthStore} from '@/stores/authStore.js';
import {GoogleLogin} from "vue3-google-login";
const authStore = useAuthStore();
const errorSnackBar = ref(false);
async function googleCallback(token) {
const response = await authStore.loginWithGoogle(JSON.stringify(token));
if (response !== true) {
errorSnackBar.value = true;
}
}
</script>

View File

@@ -0,0 +1,159 @@
<script setup>
import SubscriptionList from "@/views/creators/SubscriptionList.vue";
import { useAuthStore } from "@/stores/authStore.js";
import { useRouter } from 'vue-router';
import { computed, ref } from "vue";
import { useI18n } from 'vue-i18n';
import { useCreatorProfileStore } from "@/stores/creatorProfileStore.js";
import {useUserProfileStore} from "@/stores/userProfileStore.js";
import {useSideBarStore} from "@/stores/sideBarStore.js";
const {locale} = useI18n();
const router = useRouter();
const selectedLanguage = ref(locale.value);
const userProfileStore = useUserProfileStore();
const creatorProfileStore = useCreatorProfileStore();
const authStore = useAuthStore();
const sideBarStore = useSideBarStore();
const creatorIsCurrentUser = computed(() => authStore.isAuthenticated && authStore.userId);
const createHtmlContent = () => {
router.push('/content/editor');
};
function initializeLocale() {
const preferredLocale = localStorage.getItem('preferredLocale');
selectedLanguage.value = preferredLocale === null ? locale.value : preferredLocale;
locale.value = selectedLanguage.value;
}
function toggleLanguage() {
const lang = selectedLanguage.value === 'fr' ? 'en' : 'fr';
locale.value = lang;
selectedLanguage.value = lang;
localStorage.setItem('preferredLocale', lang);
}
function toggleMenu() {
sideBarStore.toggle();
}
initializeLocale();
</script>
<template>
<nav :class="['fixed flex flex-col h-full bg-white border-r border-gray-300', sideBarStore.isOpen ? 'max-w-64 px-4' : 'max-w-[82px] px-2']">
<!-- LOGO HUTOPY -->
<div class="mt-4" :class="sideBarStore.isOpen ? 'px-4' : 'px-2'">
<router-link to="/@hutopy">
<img v-if="sideBarStore.isOpen"
src="/images/hutopymedia/banners/hutopy.png"
alt="hutopy"
width="300px"
height="64px">
<img v-else
src="/images/usersmedia/HutopyProfile/profilepictures/profileHutopyProfile01.png"
alt="hutopy"
width="42px"
height="42px">
</router-link>
</div>
<!-- <div class="flex-grow mt-4" :class="sideBarStore.isOpen ? 'px-4' : 'px-2'">-->
<!-- <template v-if="authStore.isAuthenticated"> -->
<!-- <div class="font-bold" :class="{ 'text-center': !sideBarStore.isOpen }">-->
<!-- <span v-if="sideBarStore.isOpen">{{ $t('sidebar.subscriptionTitle') }}</span>-->
<!-- <span v-else>A</span>-->
<!-- </div>-->
<!-- <div-->
<!-- class="border-b border-gray-300 my-4 mx-auto"-->
<!-- :class="sideBarStore.isOpen ? 'w-48' : 'w-16'"-->
<!-- ></div>-->
<!-- <subscription-list v-if="sideBarStore.isOpen"></subscription-list>-->
<!-- </template>-->
<!-- </div>-->
<div
class="border-b border-gray-300 my-4 mx-auto"
:class="sideBarStore.isOpen ? 'w-48' : 'w-16'"
></div>
<div class="flex-grow"></div>
<!-- SECTION UTILISATEUR -->
<div :class="sideBarStore.isOpen ? 'px-4' : 'px-2'">
<div class="flex items-center justify-start p-2 mb-4" :class="!sideBarStore.isOpen ? 'my-2' : ''">
<img
:src="userProfileStore.portraitUrl"
alt="Profile Image"
referrerpolicy="no-referrer"
class="rounded-full"
width="42"
height="42"
style="max-height: 42px;">
<span v-if="sideBarStore.isOpen" class="ml-2 text-sm font-sans capitalize">
{{ userProfileStore.alias }}
</span>
</div>
<div class="flex flex-col gap-4 mb-4">
<!-- <router-link v-if="creatorProfileStore.hasCreator" :to="`/content/editor`">-->
<!-- <v-btn class="w-full justify-start" prepend-icon="mdi-pencil" variant="flat" :class="!sideBarStore.isOpen ? 'my-2' : ''">-->
<!-- <span v-if="sideBarStore.isOpen">Éditeur</span>-->
<!-- </v-btn>-->
<!-- </router-link>-->
<router-link v-if="creatorProfileStore.hasCreator" :to="`/@${creatorProfileStore.creator.name}`">
<v-btn class="w-full justify-start" prepend-icon="mdi-home-account" variant="flat" :class="!sideBarStore.isOpen ? 'my-2' : ''">
<span v-if="sideBarStore.isOpen">{{ creatorProfileStore.creator.name }}</span>
</v-btn>
</router-link>
<router-link v-else-if="authStore.isAuthenticated"
class="w-full justify-start"
to="/create-creator">
<v-btn class="w-full" variant="plain" :class="!sideBarStore.isOpen ? 'my-2' : ''">
<span v-if="sideBarStore.isOpen">Activer votre page</span>
</v-btn>
</router-link>
<div v-if="authStore.isAuthenticated">
<v-btn to="/profile" class="w-full justify-start" prepend-icon="mdi-account" variant="flat" :class="!sideBarStore.isOpen ? 'my-2' : ''">
<span v-if="sideBarStore.isOpen">{{ $t('header.myprofile') }}</span>
</v-btn>
</div>
<v-btn variant="flat" class="w-full justify-start" prepend-icon="mdi-translate-variant" @click="toggleLanguage" :class="!sideBarStore.isOpen ? 'my-2' : ''">
<span v-if="sideBarStore.isOpen">{{ $t('language.language') }}</span>
</v-btn>
<v-btn
@click="toggleMenu"
variant="flat"
class="w-full justify-start"
:prepend-icon="sideBarStore.isOpen ? 'mdi-arrow-collapse-left' : 'mdi-arrow-collapse-right'"
:class="!sideBarStore.isOpen ? 'my-2' : ''"
>
<span v-if="sideBarStore.isOpen">{{ $t('sidebar.Reduce') }}</span>
</v-btn>
<div v-if="!authStore.isAuthenticated">
<v-btn to="/login" variant="flat" class="justify-start" prepend-icon="mdi-login" :class="!sideBarStore.isOpen ? 'my-2' : ''">
<span v-if="sideBarStore.isOpen">{{ $t('sidebar.connection') }}</span>
</v-btn>
</div>
<div v-else>
<v-btn @click="authStore.logout" variant="flat" class="justify-start" prepend-icon="mdi-logout" :class="!sideBarStore.isOpen ? 'my-2' : ''">
<span v-if="sideBarStore.isOpen">{{ $t('header.Signout') }}</span>
</v-btn>
</div>
</div>
</div>
</nav>
</template>

View File

@@ -0,0 +1,121 @@
<template>
<v-container class="mt-10 bg-gray-100 py-10 rounded-lg shadow-lg border border-fuchsia-500 mb-15">
<div class="flex justify-center text-6xl mb-12 font-sans font-weight-bold">Portefeuille</div>
<div class="flex justify-between mb-4">
<div class="text-left">
<span class="font-bold">Montant Total : {{ formattedBalance }}</span>
</div>
<div class="text-right">
<span class="font-bold">Transactions total : {{ transactionCount }}</span>
</div>
</div>
<v-data-table
:headers="headers"
:items="formattedTransactions"
class="elevation-1 text-black"
:items-per-page="5"
show-group-by
>
</v-data-table>
<div class="flex justify-end mt-4">
<v-btn icon @click="openModal">
<v-icon class="text-[#A30E79]">mdi-information</v-icon>
</v-btn>
</div>
<v-dialog v-model="isModalOpen" max-width="500px">
<v-card>
<v-card-title>
Tarification
</v-card-title>
<v-card-text class="scrollable-content">
Découvrez Hutopy, l'endroit où la valorisation de votre travail atteint son apogée. Avec une commission réduite à seulement 9 %, notre engagement envers votre succès est palpable. Chaque pourcentage prélevé est réinvesti avec soin pour catalyser votre croissance : du développement de fonctionnalités innovantes à la maintenance d'une infrastructure technologique de pointe, en passant par un support utilisateur de premier ordre. Notre objectif ? Amplifier votre expansion et garantir une expérience utilisateur sans précédent.
Pour chaque transaction, un frais minime assure la sécurité et la fiabilité de vos paiements, grâce à un partenaire de confiance à la renommée mondiale. Ce dernier sécurise pour des milliards en transaction chaque année pour une diversité d'entreprises, allant des startups dynamiques aux conglomérats établis. Ce gage de sécurité est disponible pour une modique somme : 2,9 % plus 0,30 $ par transaction, une petite contribution pour la tranquillité d'esprit et la protection de vos revenus.
Notre modèle tarifaire a été pensé dans un esprit de simplicité et de transparence, avec l'ambition ultime d'optimiser vos gains. Chez Hutopy, la notion de partenariat prend tout son sens : votre épanouissement est au cœur de nos préoccupations. Bénéficiez d'une plateforme qui élargit votre horizon créatif et entrepreneurial, tout en vous assurant que vos intérêts sont précieusement gardés.
Hutopy est plus qu'une plateforme ; c'est une communauté où la transformation de la passion en profit devient réalité, grâce au soutien indéfectible d'une équipe dévouée à enrichir votre parcours. Nous vous invitons à nous rejoindre pour explorer ensemble les avenues de succès que nous pouvons emprunter ensemble, tout en vous garantissant une part conséquente de vos revenus. Embarquez dans une aventure votre présence en ligne ne connaît pas de limites, soutenue par Hutopy, votre allié dans la quête du succès.
</v-card-text>
<v-card-actions>
<v-btn text class="ml-auto" @click="isModalOpen = false">Fermer</v-btn>
</v-card-actions>
</v-card>
</v-dialog>
<div class="flex justify-center mt-4 ">
<v-btn text class="transparent-btn text-lg px-12" @click="navigateToHome">Retour</v-btn>
</div>
</v-container>
</template>
<script async setup>
import { onBeforeMount, ref, computed } from 'vue';
import { useRouter } from 'vue-router';
import {useUserProfileStore} from "@/stores/userProfileStore.js";
const userProfileStore = useUserProfileStore();
const router = useRouter();
const userTransactions = ref([]);
const totalBalance = ref("");
const isModalOpen = ref(false);
const formattedTransactions = computed(() => {
return userTransactions.value.map(transaction => ({
...transaction,
created: transaction.created.split('T')[0]
}));
});
const formattedBalance = computed(() => {
const balance = totalBalance.value.toString();
return `${balance} $`
});
const transactionCount = computed(() => userTransactions.value.length);
onBeforeMount( () => {
try {
userTransactions.value = userProfileStore.value.userTransactions;
totalBalance.value = userProfileStore.value.totalBalance;
} catch (error) {
navigateToHome();
}
});
const headers = ref([
{ title: 'Montant', value: 'amount', width: '20%', key: "amount" },
{ title: 'Date', value: 'created', width: '20%', key: "created" },
{ title: 'Message', value: 'tipMessage', width: '60%' }
]);
const navigateToHome = () => {
router.push('/');
};
const openModal = () => {
isModalOpen.value = true;
};
</script>
<style scoped>
.scrollable-content {
max-height: 600px;
overflow-y: auto;
}
.scrollable-content::-webkit-scrollbar {
width: 6px;
}
.scrollable-content::-webkit-scrollbar-thumb {
background-color: #A30E79;
border-radius: 10px;
}
.scrollable-content::-webkit-scrollbar-thumb:hover {
background-color: #a21caf;
}
.transparent-btn {
background-color: transparent;
color: inherit;
box-shadow: none;
}
</style>