Many fix and improvements
This commit is contained in:
89
package-lock.json
generated
89
package-lock.json
generated
@@ -10,6 +10,7 @@
|
||||
"dependencies": {
|
||||
"@mdi/font": "^7.4.47",
|
||||
"@stripe/stripe-js": "^3.0.10",
|
||||
"@vueuse/core": "^10.11.0",
|
||||
"@xtiannyeto/vue-auth-social": "^0.1.9",
|
||||
"axios": "^1.6.7",
|
||||
"pinia": "^2.1.7",
|
||||
@@ -889,6 +890,11 @@
|
||||
"resolved": "https://registry.npmjs.org/@types/lodash/-/lodash-4.17.7.tgz",
|
||||
"integrity": "sha512-8wTvZawATi/lsmNu10/j2hk1KEP0IvjubqPE3cu1Xz7xfXXt5oCq3SNUz4fMIP4XGF9Ky+Ue2tBA3hcS7LSBlA=="
|
||||
},
|
||||
"node_modules/@types/web-bluetooth": {
|
||||
"version": "0.0.20",
|
||||
"resolved": "https://registry.npmjs.org/@types/web-bluetooth/-/web-bluetooth-0.0.20.tgz",
|
||||
"integrity": "sha512-g9gZnnXVq7gM7v3tJCWV/qw7w+KeOlSHAhgF9RytFyifW6AF61hdT2ucrYhPq9hLs5JIryeupHV3qGk95dH9ow=="
|
||||
},
|
||||
"node_modules/@ungap/structured-clone": {
|
||||
"version": "1.2.0",
|
||||
"resolved": "https://registry.npmjs.org/@ungap/structured-clone/-/structured-clone-1.2.0.tgz",
|
||||
@@ -1012,6 +1018,89 @@
|
||||
"resolved": "https://registry.npmjs.org/@vue/shared/-/shared-3.4.33.tgz",
|
||||
"integrity": "sha512-aoRY0jQk3A/cuvdkodTrM4NMfxco8n55eG4H7ML/CRy7OryHfiqvug4xrCBBMbbN+dvXAetDDwZW9DXWWjBntA=="
|
||||
},
|
||||
"node_modules/@vueuse/core": {
|
||||
"version": "10.11.0",
|
||||
"resolved": "https://registry.npmjs.org/@vueuse/core/-/core-10.11.0.tgz",
|
||||
"integrity": "sha512-x3sD4Mkm7PJ+pcq3HX8PLPBadXCAlSDR/waK87dz0gQE+qJnaaFhc/dZVfJz+IUYzTMVGum2QlR7ImiJQN4s6g==",
|
||||
"dependencies": {
|
||||
"@types/web-bluetooth": "^0.0.20",
|
||||
"@vueuse/metadata": "10.11.0",
|
||||
"@vueuse/shared": "10.11.0",
|
||||
"vue-demi": ">=0.14.8"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/antfu"
|
||||
}
|
||||
},
|
||||
"node_modules/@vueuse/core/node_modules/vue-demi": {
|
||||
"version": "0.14.10",
|
||||
"resolved": "https://registry.npmjs.org/vue-demi/-/vue-demi-0.14.10.tgz",
|
||||
"integrity": "sha512-nMZBOwuzabUO0nLgIcc6rycZEebF6eeUfaiQx9+WSk8e29IbLvPU9feI6tqW4kTo3hvoYAJkMh8n8D0fuISphg==",
|
||||
"hasInstallScript": true,
|
||||
"bin": {
|
||||
"vue-demi-fix": "bin/vue-demi-fix.js",
|
||||
"vue-demi-switch": "bin/vue-demi-switch.js"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/antfu"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@vue/composition-api": "^1.0.0-rc.1",
|
||||
"vue": "^3.0.0-0 || ^2.6.0"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"@vue/composition-api": {
|
||||
"optional": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/@vueuse/metadata": {
|
||||
"version": "10.11.0",
|
||||
"resolved": "https://registry.npmjs.org/@vueuse/metadata/-/metadata-10.11.0.tgz",
|
||||
"integrity": "sha512-kQX7l6l8dVWNqlqyN3ePW3KmjCQO3ZMgXuBMddIu83CmucrsBfXlH+JoviYyRBws/yLTQO8g3Pbw+bdIoVm4oQ==",
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/antfu"
|
||||
}
|
||||
},
|
||||
"node_modules/@vueuse/shared": {
|
||||
"version": "10.11.0",
|
||||
"resolved": "https://registry.npmjs.org/@vueuse/shared/-/shared-10.11.0.tgz",
|
||||
"integrity": "sha512-fyNoIXEq3PfX1L3NkNhtVQUSRtqYwJtJg+Bp9rIzculIZWHTkKSysujrOk2J+NrRulLTQH9+3gGSfYLWSEWU1A==",
|
||||
"dependencies": {
|
||||
"vue-demi": ">=0.14.8"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/antfu"
|
||||
}
|
||||
},
|
||||
"node_modules/@vueuse/shared/node_modules/vue-demi": {
|
||||
"version": "0.14.10",
|
||||
"resolved": "https://registry.npmjs.org/vue-demi/-/vue-demi-0.14.10.tgz",
|
||||
"integrity": "sha512-nMZBOwuzabUO0nLgIcc6rycZEebF6eeUfaiQx9+WSk8e29IbLvPU9feI6tqW4kTo3hvoYAJkMh8n8D0fuISphg==",
|
||||
"hasInstallScript": true,
|
||||
"bin": {
|
||||
"vue-demi-fix": "bin/vue-demi-fix.js",
|
||||
"vue-demi-switch": "bin/vue-demi-switch.js"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/antfu"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@vue/composition-api": "^1.0.0-rc.1",
|
||||
"vue": "^3.0.0-0 || ^2.6.0"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"@vue/composition-api": {
|
||||
"optional": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/@xtiannyeto/vue-auth-social": {
|
||||
"version": "0.1.9",
|
||||
"resolved": "https://registry.npmjs.org/@xtiannyeto/vue-auth-social/-/vue-auth-social-0.1.9.tgz",
|
||||
|
||||
@@ -11,6 +11,7 @@
|
||||
"dependencies": {
|
||||
"@mdi/font": "^7.4.47",
|
||||
"@stripe/stripe-js": "^3.0.10",
|
||||
"@vueuse/core": "^10.11.0",
|
||||
"@xtiannyeto/vue-auth-social": "^0.1.9",
|
||||
"axios": "^1.6.7",
|
||||
"pinia": "^2.1.7",
|
||||
|
||||
|
Before Width: | Height: | Size: 17 KiB After Width: | Height: | Size: 17 KiB |
|
Before Width: | Height: | Size: 18 KiB After Width: | Height: | Size: 18 KiB |
34
src/App.vue
34
src/App.vue
@@ -1,7 +1,7 @@
|
||||
<template>
|
||||
<v-app v-if="isUserLoaded">
|
||||
<v-app>
|
||||
<div class="m-0 flex flex-column h-screen">
|
||||
<Header @toggle-sidebar="toggleSidebar" class="fixed w-full z-50 top-0 p-2"></Header>
|
||||
<Header class="fixed w-full z-50 top-0 p-2"></Header>
|
||||
<div class="flex flex-row relative">
|
||||
<div
|
||||
@mouseenter="openSidebar"
|
||||
@@ -10,7 +10,7 @@
|
||||
></div>
|
||||
|
||||
<transition name="slide-fade">
|
||||
<div v-show="!hideSideBar"
|
||||
<div v-show="sideBarStore.visible"
|
||||
@mouseleave="startCloseSidebarTimer"
|
||||
@mouseenter="clearCloseSidebarTimer"
|
||||
class=" fixed h-full min-w-60 border-r-2 bg-white z-30 transition-transform duration-700">
|
||||
@@ -31,38 +31,24 @@
|
||||
import Header from "@/views/main/Header.vue";
|
||||
import Footer from "@/views/main/Footer.vue";
|
||||
import SideBar from "@/views/main/SideBar.vue";
|
||||
import {ref, onMounted, onUnmounted, onBeforeMount} from 'vue';
|
||||
import {eventBus} from '@/eventBus.js';
|
||||
import {useUserStore} from "@/stores/user.js";
|
||||
import {useClient} from "@/plugins/api.js";
|
||||
import {ref, onMounted, onUnmounted} from 'vue';
|
||||
import {useSideBarStore} from "@/stores/sideBarStore.js";
|
||||
|
||||
const hideSideBar = ref(true);
|
||||
const isUserLoaded = ref(false);
|
||||
const showPopup = ref(false);
|
||||
const popup = ref(null);
|
||||
const popupButton = ref(null);
|
||||
let closeSidebarTimer = null;
|
||||
|
||||
let client = useClient();
|
||||
let userStore = useUserStore();
|
||||
|
||||
onBeforeMount(async () => {
|
||||
await userStore.setCurrentUser(client);
|
||||
isUserLoaded.value = true;
|
||||
});
|
||||
|
||||
const toggleSidebar = () => {
|
||||
hideSideBar.value = !hideSideBar.value;
|
||||
};
|
||||
const sideBarStore = useSideBarStore()
|
||||
|
||||
const openSidebar = () => {
|
||||
clearCloseSidebarTimer();
|
||||
hideSideBar.value = false;
|
||||
sideBarStore.show()
|
||||
};
|
||||
|
||||
const startCloseSidebarTimer = () => {
|
||||
closeSidebarTimer = setTimeout(() => {
|
||||
hideSideBar.value = true;
|
||||
sideBarStore.hide()
|
||||
}, 500);
|
||||
};
|
||||
|
||||
@@ -83,18 +69,16 @@ const handleClickOutside = (event) => {
|
||||
!event.target.closest('.w-48') &&
|
||||
!event.target.closest('.v-app-bar-nav-icon')
|
||||
) {
|
||||
hideSideBar.value = true;
|
||||
sideBarStore.hide()
|
||||
}
|
||||
};
|
||||
|
||||
onMounted(() => {
|
||||
document.addEventListener('click', handleClickOutside);
|
||||
eventBus.value.toggleSidebar = toggleSidebar;
|
||||
});
|
||||
|
||||
onUnmounted(() => {
|
||||
document.removeEventListener('click', handleClickOutside);
|
||||
eventBus.value.toggleSidebar = null;
|
||||
});
|
||||
</script>
|
||||
|
||||
|
||||
@@ -1,3 +0,0 @@
|
||||
import { ref } from 'vue';
|
||||
|
||||
export const eventBus = ref({});
|
||||
@@ -8,7 +8,6 @@ import 'vuetify/styles'
|
||||
import {createVuetify} from 'vuetify'
|
||||
import * as components from 'vuetify/components'
|
||||
import * as directives from 'vuetify/directives'
|
||||
import clientPlugin from './plugins/api.js'
|
||||
import vueGoogleOauth from 'vue3-google-login'
|
||||
|
||||
const vuetify = createVuetify({
|
||||
@@ -17,7 +16,6 @@ const vuetify = createVuetify({
|
||||
});
|
||||
|
||||
createApp(App)
|
||||
.use(clientPlugin)
|
||||
.use(createPinia())
|
||||
.use(vuetify)
|
||||
.use(router)
|
||||
|
||||
@@ -1,48 +0,0 @@
|
||||
import UserTransactionsModel from "@/models/userTransactionsModel.js";
|
||||
import SocialNetworksModel from "@/models/socialNetworksModel.js";
|
||||
import ProfileColorsModel from "@/models/profileColorsModel.js";
|
||||
import StoredDataUrlsModel from "@/models/storedDataUrlsModel.js";
|
||||
|
||||
export default class MyUserModel
|
||||
{
|
||||
id = "";
|
||||
creatorAlias = "";
|
||||
alias = null;
|
||||
firstName = "";
|
||||
lastName = "";
|
||||
userName = "";
|
||||
occupation = "";
|
||||
email = "";
|
||||
phoneNumber = "";
|
||||
birthDate = "";
|
||||
country = "";
|
||||
city = "";
|
||||
address = "";
|
||||
about = "";
|
||||
description = "";
|
||||
socialNetworks = new SocialNetworksModel();
|
||||
profileColors = new ProfileColorsModel();
|
||||
storedDataUrls = new StoredDataUrlsModel();
|
||||
totalBalance = "";
|
||||
userTransactions = [];
|
||||
|
||||
static createFromApiResult(apiResult){
|
||||
const userModel = Object.assign(new MyUserModel(), apiResult);
|
||||
|
||||
const notMapperTransaction = Object.freeze(userModel.userTransactions);
|
||||
userModel.userTransactions = [];
|
||||
|
||||
for (const transaction of notMapperTransaction) {
|
||||
userModel.userTransactions.push(UserTransactionsModel.createFromApiResult(transaction))
|
||||
}
|
||||
|
||||
return userModel;
|
||||
}
|
||||
|
||||
static getDefaultUser(){
|
||||
const defaultUser = new MyUserModel();
|
||||
defaultUser.userName = "Anonyme"
|
||||
|
||||
return defaultUser;
|
||||
}
|
||||
}
|
||||
@@ -1,11 +0,0 @@
|
||||
export default class ProfileColorsModel
|
||||
{
|
||||
bannerTop = "";
|
||||
bannerBottom = "";
|
||||
accent = "";
|
||||
menu = "";
|
||||
|
||||
static createFromApiResult(apiResult){
|
||||
return Object.assign(new ProfileColorsModel(), apiResult)
|
||||
}
|
||||
}
|
||||
@@ -1,15 +0,0 @@
|
||||
export default class SocialNetworksModel
|
||||
{
|
||||
facebookUrl = "";
|
||||
instagramUrl = "";
|
||||
xUrl = "";
|
||||
linkedInUrl = "";
|
||||
tikTokUrl = "";
|
||||
youtubeUrl = "";
|
||||
redditUrl = "";
|
||||
yourWebsiteUrl = "";
|
||||
|
||||
static createFromApiResult(apiResult){
|
||||
return Object.assign(new SocialNetworksModel(), apiResult)
|
||||
}
|
||||
}
|
||||
@@ -1,10 +0,0 @@
|
||||
export default class StoredDataUrlsModel
|
||||
{
|
||||
bannerPictureUrl = null;
|
||||
profilePictureUrl = null;
|
||||
websiteIconUrl = null;
|
||||
|
||||
static createFromApiResult(apiResult){
|
||||
return Object.assign(new StoredDataUrlsModel(), apiResult)
|
||||
}
|
||||
}
|
||||
@@ -1,9 +1,7 @@
|
||||
import axios from "axios";
|
||||
import {inject} from "vue";
|
||||
import axios from "axios"
|
||||
import {useAuthStore} from "@/stores/authStore.js"
|
||||
|
||||
const key = Symbol("api");
|
||||
|
||||
export default function(app) {
|
||||
export function useClient() {
|
||||
if (!import.meta.env.VITE_API_URL) throw new Error("VITE_API_URL is not provided")
|
||||
|
||||
// You create a .env.development file and a .env file
|
||||
@@ -13,23 +11,16 @@ export default function(app) {
|
||||
timeout: 10000,
|
||||
});
|
||||
|
||||
const authStore = useAuthStore()
|
||||
const requestInterceptor = (config) => {
|
||||
const token = localStorage.getItem("jwt");
|
||||
if (token) config.headers["Authorization"] = `Bearer ${token}`;
|
||||
return config;
|
||||
if (authStore.isAuthenticated) {
|
||||
config.headers["Authorization"] = `Bearer ${authStore.accessToken}`
|
||||
}
|
||||
return config
|
||||
}
|
||||
|
||||
api.interceptors.request.use(requestInterceptor);
|
||||
|
||||
// This is a local injection, to use it in your components you can do this:
|
||||
// const api = inject("api")
|
||||
// api.get("/some-endpoint")
|
||||
app.provide(key, api)
|
||||
}
|
||||
|
||||
export function useClient() {
|
||||
const api = inject(key)
|
||||
if (!api) throw new Error("api is not provided")
|
||||
return api;
|
||||
}
|
||||
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
import { createRouter, createWebHistory } from 'vue-router'
|
||||
import { useUserStore } from "@/stores/user.js";
|
||||
import GuillaumeAime from '@/views/manualusers/GuillaumeAime.vue'
|
||||
import About from '@/views/documentation/About.vue'
|
||||
import ContentPolicy from '@/views/documentation/ContentPolicy.vue'
|
||||
@@ -22,6 +21,7 @@ import CreatorList from '../views/creators/CreatorList.vue'
|
||||
import CreatorPage from "@/views/creators/CreatorPage.vue";
|
||||
import ContentPage from "@/views/contents/ContentPage.vue";
|
||||
import PostContent from "@/views/contents/PostContent.vue";
|
||||
import {useAuthStore} from "@/stores/authStore.js";
|
||||
|
||||
const routes = [
|
||||
{
|
||||
@@ -114,18 +114,6 @@ const routes = [
|
||||
component: Join,
|
||||
meta: { hideSideBar: true }
|
||||
},
|
||||
|
||||
{
|
||||
path: '/paymentcompleted',
|
||||
name: 'PayementCompleted',
|
||||
component: PaymentCompleted
|
||||
},
|
||||
{
|
||||
path: '/profile',
|
||||
name: 'profile',
|
||||
component: Profile,
|
||||
meta: { requiresAuth: true }
|
||||
},
|
||||
{
|
||||
path: '/signup',
|
||||
name: 'signup',
|
||||
@@ -136,12 +124,23 @@ const routes = [
|
||||
name: 'login',
|
||||
component: LoginView
|
||||
},
|
||||
{
|
||||
path: '/paymentcompleted',
|
||||
name: 'PayementCompleted',
|
||||
component: PaymentCompleted,
|
||||
},
|
||||
{
|
||||
path: '/wallet',
|
||||
name: 'wallet',
|
||||
component: Wallet,
|
||||
meta: { requiresAuth: true }
|
||||
}
|
||||
meta: { requiresAuth: true }
|
||||
},
|
||||
{
|
||||
path: '/profile',
|
||||
name: 'profile',
|
||||
component: Profile,
|
||||
meta: { requiresAuth: true }
|
||||
},
|
||||
]
|
||||
|
||||
const router = createRouter({
|
||||
@@ -151,11 +150,10 @@ const router = createRouter({
|
||||
|
||||
// Navigation gards
|
||||
router.beforeEach((to, from, next) => {
|
||||
const authStore = useUserStore();
|
||||
const authStore = useAuthStore();
|
||||
|
||||
|
||||
if (to.matched.some(record => record.meta.requiresAuth)) {
|
||||
if (!authStore.user.value || !Object.keys(authStore.user.value).length) {
|
||||
if (!authStore.isAuthenticated) {
|
||||
next('/');
|
||||
} else {
|
||||
next();
|
||||
|
||||
@@ -1,67 +0,0 @@
|
||||
import { defineStore } from 'pinia';
|
||||
|
||||
const baseUrl = '/api';
|
||||
|
||||
export const auth = defineStore({
|
||||
id: 'auth',
|
||||
|
||||
state: () => ({
|
||||
user: "",
|
||||
refreshTokenTimeout: 0
|
||||
}),
|
||||
|
||||
actions: {
|
||||
// TODO: Fix login methods
|
||||
|
||||
async login(client, email, password) {
|
||||
const requestBody = {
|
||||
email: email,
|
||||
password: password
|
||||
};
|
||||
const response = await client.post(`${baseUrl}/users/login`, requestBody)
|
||||
this.user = {
|
||||
accessToken: response.data.accessToken,
|
||||
refreshToken: response.data.refreshToken,
|
||||
}
|
||||
localStorage.setItem('jwt', this.user.accessToken);
|
||||
|
||||
this.startRefreshTokenTimer();
|
||||
},
|
||||
|
||||
async loginGoogle(client, accessToken) {
|
||||
const response = await client.post(`${baseUrl}/google/sign-in`, {accessToken: accessToken})
|
||||
this.user = {
|
||||
accessToken: response.data.accessToken,
|
||||
refreshToken: response.data.refreshToken,
|
||||
email: response.data.email
|
||||
}
|
||||
localStorage.setItem('jwt', this.user.accessToken);
|
||||
|
||||
this.startRefreshTokenTimer();
|
||||
},
|
||||
|
||||
logout() {
|
||||
localStorage.setItem('jwt', '');
|
||||
this.user = null;
|
||||
|
||||
this.stopRefreshTokenTimer();
|
||||
},
|
||||
|
||||
async refreshToken(client) {
|
||||
const response = await client.post(`${baseUrl}/users/refresh`);
|
||||
this.user.accessToken = response.accessToken;
|
||||
localStorage.setItem('jwt', this.user.accessToken);
|
||||
|
||||
this.startRefreshTokenTimer();
|
||||
},
|
||||
|
||||
startRefreshTokenTimer() {
|
||||
const timeout = 50 * 1000;
|
||||
this.refreshTokenTimeout = setTimeout(this.refreshToken, timeout);
|
||||
},
|
||||
|
||||
stopRefreshTokenTimer() {
|
||||
clearTimeout(this.refreshTokenTimeout);
|
||||
}
|
||||
}
|
||||
});
|
||||
84
src/stores/authStore.js
Normal file
84
src/stores/authStore.js
Normal file
@@ -0,0 +1,84 @@
|
||||
import {defineStore} from 'pinia';
|
||||
import {computed, ref} from "vue";
|
||||
import {useRouter} from "vue-router";
|
||||
import {useClient} from "@/plugins/api.js";
|
||||
import {useSessionStorage} from "@vueuse/core";
|
||||
|
||||
export const useAuthStore = defineStore(
|
||||
'auth',
|
||||
() => {
|
||||
const clientApi = useClient()
|
||||
const router = useRouter()
|
||||
const accessToken = useSessionStorage('auth-accessToken', undefined)
|
||||
const refreshToken = useSessionStorage('auth-refreshToken', undefined)
|
||||
const isAuthenticated = computed(() => !!accessToken.value)
|
||||
|
||||
function updateTokens(data) {
|
||||
accessToken.value = data.accessToken
|
||||
refreshToken.value = data.refreshToken
|
||||
}
|
||||
|
||||
function cleanTokens() {
|
||||
updateTokens({
|
||||
accessToken: undefined,
|
||||
refreshToken: undefined,
|
||||
})
|
||||
}
|
||||
|
||||
function logout() {
|
||||
cleanTokens()
|
||||
router.push('/')
|
||||
}
|
||||
|
||||
async function login(email, password) {
|
||||
try {
|
||||
const response = await clientApi.post(
|
||||
'api/users/login',
|
||||
{
|
||||
email: email,
|
||||
password: password
|
||||
})
|
||||
updateTokens(response.data)
|
||||
return true
|
||||
} catch (error) {
|
||||
console.error(error)
|
||||
cleanTokens()
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
async function loginGoogle(accessToken) {
|
||||
try {
|
||||
const response = await clientApi.post(
|
||||
'api/google/sign-in',
|
||||
{
|
||||
accessToken: accessToken
|
||||
})
|
||||
updateTokens(response.data)
|
||||
} catch (error) {
|
||||
console.error(error)
|
||||
cleanTokens()
|
||||
}
|
||||
}
|
||||
|
||||
async function refresh() {
|
||||
try {
|
||||
const response = await clientApi.post(
|
||||
'api/users/refresh',
|
||||
{
|
||||
refreshToken: refreshToken
|
||||
});
|
||||
|
||||
updateTokens({
|
||||
accessToken: response.accessToken,
|
||||
refreshToken: refreshToken
|
||||
})
|
||||
} catch (error) {
|
||||
console.error(error)
|
||||
cleanTokens()
|
||||
}
|
||||
}
|
||||
|
||||
return {accessToken, refreshToken, isAuthenticated, login, loginGoogle, logout}
|
||||
})
|
||||
|
||||
23
src/stores/sideBarStore.js
Normal file
23
src/stores/sideBarStore.js
Normal file
@@ -0,0 +1,23 @@
|
||||
import {computed, ref} from 'vue';
|
||||
import {defineStore} from "pinia";
|
||||
|
||||
export const useSideBarStore = defineStore(
|
||||
'sideBar',
|
||||
() => {
|
||||
const state = ref(false)
|
||||
const visible = computed(() => state.value)
|
||||
|
||||
function toggle() {
|
||||
state.value = !state.value
|
||||
}
|
||||
|
||||
function show() {
|
||||
state.value = true
|
||||
}
|
||||
|
||||
function hide() {
|
||||
state.value = false
|
||||
}
|
||||
|
||||
return {visible, toggle, show, hide}
|
||||
})
|
||||
@@ -1,73 +0,0 @@
|
||||
import { ref } from 'vue'
|
||||
import { defineStore } from 'pinia'
|
||||
import MyUserModel from "@/models/myUserModel.js";
|
||||
|
||||
export const useUserStore = defineStore('user', () => {
|
||||
const user = ref({});
|
||||
const hasChanged = ref(false);
|
||||
|
||||
function getCurrentUser() {
|
||||
return this.user.value;
|
||||
}
|
||||
async function setCurrentUser(client) {
|
||||
try {
|
||||
const myUser = await client.get("/api/GetMyUser");
|
||||
this.user.value = MyUserModel.createFromApiResult(myUser.data);
|
||||
this.hasChanged = false;
|
||||
} catch (e){
|
||||
this.user.value = MyUserModel.getDefaultUser();
|
||||
console.log("User not logged.")
|
||||
}
|
||||
}
|
||||
|
||||
async function updateCurrentUser(client, myUserModel, profilePicture, bannerPicture, websiteIcon) {
|
||||
await client.patch("/api/UpdateMyUser/profile", myUserModel)
|
||||
|
||||
if (typeof myUserModel.storedDataUrls.profilePictureUrl !== "object") {
|
||||
const haveNewProfilePicture = profilePicture !== null && profilePicture.size !== 0;
|
||||
const updateProfilePictureEndpoint = haveNewProfilePicture ? `/api/UpdateMyUser/profile-picture` : `/api/UpdateMyUser/profile-picture?url=${myUserModel.storedDataUrls.profilePictureUrl}`;
|
||||
const response = await client.post(updateProfilePictureEndpoint, profilePicture, {
|
||||
headers: {
|
||||
'Content-Type': profilePicture?.type ?? "application/octet-stream",
|
||||
}
|
||||
});
|
||||
|
||||
if (haveNewProfilePicture) {
|
||||
this.user.value.storedDataUrls.profilePictureUrl = response.data;
|
||||
}
|
||||
}
|
||||
if (typeof myUserModel.storedDataUrls.bannerPictureUrl !== "object") {
|
||||
const haveNewBannerPicture = bannerPicture !== null && bannerPicture.size !== 0;
|
||||
|
||||
const updateBannerPictureEndpoint = haveNewBannerPicture ? `/api/UpdateMyUser/banner-picture` : `/api/UpdateMyUser/banner-picture?url=${myUserModel.storedDataUrls.bannerPictureUrl}`;
|
||||
const response = await client.post(updateBannerPictureEndpoint, bannerPicture, {
|
||||
headers: {
|
||||
'Content-Type': bannerPicture?.type ?? "octet-stream",
|
||||
}
|
||||
});
|
||||
|
||||
if (haveNewBannerPicture) {
|
||||
this.user.value.storedDataUrls.bannerPictureUrl = response.data;
|
||||
}
|
||||
}
|
||||
if (typeof myUserModel.storedDataUrls.websiteIconUrl !== "object") {
|
||||
const haveNewWebsiteIcon = websiteIcon !== null && websiteIcon.size !== 0;
|
||||
|
||||
const updateWebsiteIconEndpoint = haveNewWebsiteIcon ? `/api/UpdateMyUser/website-icon` : `/api/UpdateMyUser/website-icon?url=${myUserModel.storedDataUrls.websiteIconUrl}`;
|
||||
const response = await client.post(updateWebsiteIconEndpoint, websiteIcon, {
|
||||
headers: {
|
||||
'Content-Type': websiteIcon?.type ?? "application/octet-stream",
|
||||
}
|
||||
});
|
||||
|
||||
if (haveNewWebsiteIcon) {
|
||||
this.user.value.storedDataUrls.websiteIconUrl = response.data;
|
||||
}
|
||||
}
|
||||
|
||||
this.user.value = myUserModel;
|
||||
this.hasChanged = true;
|
||||
}
|
||||
|
||||
return { user, getCurrentUser, setCurrentUser, updateCurrentUser }
|
||||
})
|
||||
77
src/stores/userStore.js
Normal file
77
src/stores/userStore.js
Normal file
@@ -0,0 +1,77 @@
|
||||
import {computed, watch} from 'vue'
|
||||
import {defineStore} from 'pinia'
|
||||
import {useAuthStore} from "@/stores/authStore.js";
|
||||
import {useClient} from "@/plugins/api.js";
|
||||
import {useSessionStorage} from "@vueuse/core";
|
||||
|
||||
export const useUserStore = defineStore(
|
||||
'user',
|
||||
() => {
|
||||
const authStore = useAuthStore()
|
||||
const authWatcher = watch(
|
||||
() => authStore.isAuthenticated,
|
||||
async (newValue, oldValue) => {
|
||||
if (newValue) {
|
||||
await fetchCurrentUserProfile()
|
||||
} else {
|
||||
user.value = undefined
|
||||
creator.value = undefined
|
||||
}
|
||||
})
|
||||
|
||||
const user = useSessionStorage('user-user', {}, {writeDefaults: false})
|
||||
const creator = useSessionStorage('user-creator', {}, {writeDefaults: false})
|
||||
|
||||
const alias = computed(() => {
|
||||
if (user.value) {
|
||||
return user.value.alias || `${user.value.firstName || ''} ${user.value.lastName || ''}`.trim() || 'Anonyme'
|
||||
}
|
||||
return 'Anonyme';
|
||||
})
|
||||
const portraitUrl = computed(() => {
|
||||
return user.value && user.value.portraitUrl
|
||||
? user.value.portraitUrl
|
||||
: '/images/usersmedia/anonyme/profilepictures/profileAnonymeSquare.png'
|
||||
})
|
||||
|
||||
async function fetchCurrentUserProfile() {
|
||||
try {
|
||||
const client = useClient()
|
||||
const userResponse = await client.get("/api/GetMyUser");
|
||||
user.value = userResponse.data
|
||||
|
||||
try {
|
||||
const creatorId = userResponse.data.id
|
||||
const creatorResponse = await client.get(`/api/creators/${creatorId}`)
|
||||
creator.value = creatorResponse.data
|
||||
} catch (error) {
|
||||
creator.value = undefined
|
||||
}
|
||||
} catch (error) {
|
||||
user.value = undefined;
|
||||
}
|
||||
}
|
||||
|
||||
async function updateCurrentUser(userModel, profilePicture) {
|
||||
const client = useClient()
|
||||
await client.patch("/api/UpdateMyUser/profile", userModel)
|
||||
|
||||
if (typeof userModel.storedDataUrls.profilePictureUrl !== "object") {
|
||||
const haveNewProfilePicture = profilePicture !== null && profilePicture.size !== 0;
|
||||
const updateProfilePictureEndpoint = haveNewProfilePicture ? `/api/UpdateMyUser/profile-picture` : `/api/UpdateMyUser/profile-picture?url=${userModel.storedDataUrls.profilePictureUrl}`;
|
||||
const response = await client.post(updateProfilePictureEndpoint, profilePicture, {
|
||||
headers: {
|
||||
'Content-Type': profilePicture?.type ?? "application/octet-stream",
|
||||
}
|
||||
});
|
||||
|
||||
if (haveNewProfilePicture) {
|
||||
this.user.value.portraitUrl = response.data;
|
||||
}
|
||||
}
|
||||
|
||||
this.user.value = userModel;
|
||||
}
|
||||
|
||||
return {user, creator, alias, portraitUrl}
|
||||
})
|
||||
@@ -2,107 +2,107 @@
|
||||
<div class="hidden sm:block" style="height: 40px"></div>
|
||||
|
||||
<div>
|
||||
<div class="flex flex-col lg:flex-row items-center justify-center">
|
||||
<div class="max-w-[700px] min-w-[300px]">
|
||||
<img class="rounded-none sm:rounded-2xl sm:w-full mr-8" src="/images/hutopymedia/loginpage/loginhutopy.png" alt="hutopy login">
|
||||
</div>
|
||||
<div class="flex flex-col lg:flex-row items-center justify-center">
|
||||
<div class="max-w-[700px] min-w-[300px]">
|
||||
<img class="rounded-none sm:rounded-2xl sm:w-full mr-8" src="/images/hutopymedia/loginpage/loginhutopy.png"
|
||||
alt="hutopy login">
|
||||
</div>
|
||||
|
||||
<div class="flex flex-col items-center min-w-[300px] m-12">
|
||||
<h1 class="text-center text-2xl font-bold mb-5">Connexion</h1>
|
||||
|
||||
<google-login class="w-full" :callback="googleCallback" popup-type="TOKEN">
|
||||
<template #default>
|
||||
<v-btn density="comfortable" class="mb-2 w-full">
|
||||
<v-icon left>mdi-google</v-icon>
|
||||
Google
|
||||
<div class="flex flex-col items-center min-w-[300px] m-12">
|
||||
<h1 class="text-center text-2xl font-bold mb-5">Connexion</h1>
|
||||
|
||||
<google-login class="w-full" :callback="googleCallback" popup-type="TOKEN">
|
||||
<template #default>
|
||||
<v-btn density="comfortable" class="mb-2 w-full">
|
||||
<v-icon left>mdi-google</v-icon>
|
||||
Google
|
||||
</v-btn>
|
||||
</template>
|
||||
</google-login>
|
||||
|
||||
<!-- <v-btn density="comfortable" class="mb-2 w-full">-->
|
||||
<!-- <v-icon left>mdi-facebook</v-icon>-->
|
||||
<!-- Facebook-->
|
||||
<!-- </v-btn>-->
|
||||
|
||||
<div class="w-full h-0.5 mt-4 mb-4" :style="{ backgroundColor: '#A30E79' }"></div>
|
||||
|
||||
<v-btn density="comfortable" class="mb-2 w-full" @click="showEmailForm = !showEmailForm">
|
||||
<v-icon left>mdi-account</v-icon>
|
||||
Utilisateur
|
||||
</v-btn>
|
||||
|
||||
<div v-if="showEmailForm" class="w-full mt-2">
|
||||
<v-text-field v-model="email"
|
||||
label="Courriel"
|
||||
variant="outlined"
|
||||
dense
|
||||
prepend-inner-icon="mdi-email"
|
||||
color="transparent"
|
||||
class="text-black"
|
||||
></v-text-field>
|
||||
|
||||
<v-text-field v-model="password"
|
||||
label="Mot de passe"
|
||||
:type="showPassword ? 'text' : 'password'"
|
||||
variant="outlined"
|
||||
dense
|
||||
prepend-inner-icon="mdi-lock"
|
||||
append-inner-icon="mdi-eye"
|
||||
@click:append-inner="showPassword = !showPassword"
|
||||
color="transparent"
|
||||
class="text-black"
|
||||
></v-text-field>
|
||||
|
||||
<v-btn class="w-full text-center text-white" :style="{ backgroundColor: '#A30E79' }" @click="login">
|
||||
Connecter
|
||||
</v-btn>
|
||||
</template>
|
||||
</google-login>
|
||||
|
||||
<!-- <v-btn density="comfortable" class="mb-2 w-full">-->
|
||||
<!-- <v-icon left>mdi-facebook</v-icon>-->
|
||||
<!-- Facebook-->
|
||||
<!-- </v-btn>-->
|
||||
|
||||
<div class="w-full h-0.5 mt-4 mb-4" :style="{ backgroundColor: '#A30E79' }"></div>
|
||||
<p class="mt-4 text-sm text-center">
|
||||
Si vous n'avez pas de compte, <a href="/register" class="text-blue-500">cliquez ici</a> pour en créer un.
|
||||
</p>
|
||||
|
||||
<v-btn density="comfortable" class="mb-2 w-full" @click="showEmailForm = !showEmailForm">
|
||||
<v-icon left>mdi-account</v-icon>
|
||||
Utilisateur
|
||||
</v-btn>
|
||||
|
||||
<div v-if="showEmailForm" class="w-full mt-2">
|
||||
<v-text-field v-model="user.email"
|
||||
label="Courriel"
|
||||
variant="outlined"
|
||||
dense
|
||||
prepend-inner-icon="mdi-email"
|
||||
color="transparent"
|
||||
class="text-black"
|
||||
></v-text-field>
|
||||
|
||||
<v-text-field v-model="user.password"
|
||||
label="Mot de passe"
|
||||
:type="showPassword ? 'text' : 'password'"
|
||||
variant="outlined"
|
||||
dense
|
||||
prepend-inner-icon="mdi-lock"
|
||||
append-inner-icon="mdi-eye"
|
||||
@click:append-inner="showPassword = !showPassword"
|
||||
color="transparent"
|
||||
class="text-black"
|
||||
></v-text-field>
|
||||
|
||||
<v-btn class="w-full text-center text-white" :style="{ backgroundColor: '#A30E79' }" @click="login">Connecter</v-btn>
|
||||
|
||||
<p class="mt-4 text-sm text-center">
|
||||
Si vous n'avez pas de compte, <a href="/register" class="text-blue-500">cliquez ici</a> pour en créer un.
|
||||
</p>
|
||||
|
||||
<div v-if="errorSnackBar" class="mb-4 text-red-600">
|
||||
Nom d'utilisateur ou mot de passe invalide.
|
||||
<button class="text-red-600 ml-4" @click="errorSnackBar = false">Fermer</button>
|
||||
<div v-if="errorSnackBar" class="mb-4 text-red-600">
|
||||
Nom d'utilisateur ou mot de passe invalide.
|
||||
<button class="text-red-600 ml-4" @click="errorSnackBar = false">Fermer</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
|
||||
<selected-footer></selected-footer>
|
||||
|
||||
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref } from 'vue';
|
||||
import { useRouter } from 'vue-router';
|
||||
import { useClient } from "@/plugins/api.js";
|
||||
import { auth } from '@/stores/auth.js';
|
||||
import { GoogleLogin } from "vue3-google-login";
|
||||
import {ref} from 'vue';
|
||||
import {useRouter} from 'vue-router';
|
||||
import {useAuthStore} from '@/stores/authStore.js';
|
||||
import {GoogleLogin} from "vue3-google-login";
|
||||
// import { FacebookAuth } from '@xtiannyeto/vue-auth-social';
|
||||
import SelectedFooter from "@/views/main/SelectedFooter.vue";
|
||||
|
||||
const api = useClient();
|
||||
const store = auth();
|
||||
const authStore = useAuthStore();
|
||||
const router = useRouter();
|
||||
|
||||
const user = ref({});
|
||||
const email = ref("");
|
||||
const password = ref("");
|
||||
|
||||
const errorSnackBar = ref(false);
|
||||
const showEmailForm = ref(false);
|
||||
const showPassword = ref(false);
|
||||
|
||||
async function login() {
|
||||
try {
|
||||
await store.login(api, user.value.email, user.value.password);
|
||||
await router.push('/');
|
||||
window.location.reload();
|
||||
} catch (error) {
|
||||
errorSnackBar.value = true;
|
||||
const result = await authStore.login(email.value, password.value);
|
||||
if (result === true) {
|
||||
await router.push('/')
|
||||
}
|
||||
}
|
||||
|
||||
const googleCallback = async (response) => {
|
||||
await store.loginGoogle(api, response["access_token"]);
|
||||
await authStore.loginGoogle(response["access_token"]);
|
||||
await router.push("/");
|
||||
};
|
||||
|
||||
@@ -110,4 +110,5 @@ const googleCallback = async (response) => {
|
||||
// const facebookCallback = (response) => {
|
||||
// console.log("User Successfully Logged In", response);
|
||||
// };
|
||||
|
||||
</script>
|
||||
|
||||
@@ -106,7 +106,7 @@
|
||||
<button
|
||||
@click="openModal('ModalTikTok')"
|
||||
class="HoverBtn active:bg-gray-300 py-2 px-4 border-gray-400 shadow flex items-center transition duration-200 ease-in-out w-full ">
|
||||
<span class="flex-none pa-2 min-w-32 text-left"> <img src="/images/hutopymedia/icons/black/tiktokblack.png" class="w-5 h-5" ></span>
|
||||
<span class="flex-none pa-2 min-w-32 text-left"> <img src="/images/externals/tiktok-black.png" class="w-5 h-5" ></span>
|
||||
<span class="flex-auto text-left pr-6">TikTok</span>
|
||||
<span class="flex-none">
|
||||
<v-icon>mdi-chevron-right</v-icon>
|
||||
|
||||
@@ -1,45 +1,86 @@
|
||||
<template>
|
||||
<script setup>
|
||||
|
||||
import {computed, reactive, onMounted} from 'vue';
|
||||
import {time_ago} from "@/internal_time_ago.js";
|
||||
import MessageList from "@/views/messages/MessageList.vue";
|
||||
import PostMessage from "@/views/messages/PostMessage.vue";
|
||||
|
||||
import YoutubePlayer from './YoutubePlayer.vue';
|
||||
import ImageViewer from './ImageViewer.vue';
|
||||
// import VimeoPlayer from '@/components/VimeoPlayer.vue';
|
||||
// import AudioPlayer from '@/components/AudioPlayer.vue';
|
||||
// import BlogViewer from '@/components/BlogViewer.vue';
|
||||
|
||||
const props = defineProps({
|
||||
content: {
|
||||
type: Object,
|
||||
required: true,
|
||||
}
|
||||
});
|
||||
|
||||
const hasUrls = computed(() => !!props.content.urls && props.content.urls.length > 0);
|
||||
|
||||
|
||||
const messages = reactive([]);
|
||||
|
||||
function addMessage(newMessage) {
|
||||
messages.unshift(newMessage)
|
||||
}
|
||||
|
||||
function getComponent(url) {
|
||||
if (url.includes('youtube.com') || url.includes('youtu.be')) {
|
||||
return YoutubePlayer;
|
||||
// } else if (url.includes('vimeo.com')) {
|
||||
// return VimeoPlayer;
|
||||
} else if (url.match(/\.(jpeg|jpg|gif|png)$/)) {
|
||||
return ImageViewer;
|
||||
// } else if (url.match(/\.(mp3|wav|ogg)$/)) {
|
||||
// return 'AudioPlayer';
|
||||
// } else {
|
||||
// return 'BlogViewer';
|
||||
}
|
||||
}
|
||||
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="shadow-md rounded-lg bg-gray-50">
|
||||
|
||||
<div class="text-lg font-bold">
|
||||
{{ props.content.title }}
|
||||
<span class="text-md-caption">
|
||||
{{ time_ago(props.content.createdAt) }}
|
||||
<v-card
|
||||
outlined
|
||||
tile
|
||||
>
|
||||
<v-card-title>
|
||||
{{ props.content.title }}
|
||||
<span class="text-subtitle-2">
|
||||
{{ time_ago(props.content.createdAt) }}
|
||||
</span>
|
||||
</div>
|
||||
</v-card-title>
|
||||
|
||||
<div v-if="props.content.url !== null || props.content.uri !== null"
|
||||
class="h-48 object-cover bg-gray-300 rounded-md">
|
||||
<v-carousel v-if="hasUrls">
|
||||
|
||||
<v-img :src="props.content.url"
|
||||
v-if="!isHttpUrl">
|
||||
</v-img>
|
||||
<v-carousel-item
|
||||
v-for="url in props.content.urls"
|
||||
:key="url"
|
||||
>
|
||||
<component :is="getComponent(url)"
|
||||
:src="url"
|
||||
></component>
|
||||
</v-carousel-item>
|
||||
|
||||
<iframe v-if="isHttpUrl"
|
||||
:src="props?.content?.uri"
|
||||
title="YouTube video player"
|
||||
allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share"
|
||||
referrerpolicy="strict-origin-when-cross-origin"
|
||||
allowfullscreen>
|
||||
</iframe>
|
||||
</v-carousel>
|
||||
|
||||
</div>
|
||||
<v-card-text>
|
||||
{{ props.content.description }}
|
||||
</v-card-text>
|
||||
|
||||
|
||||
<div class="flex flex-row">
|
||||
|
||||
<div>
|
||||
<div class="text-sm text-gray-500">{{ props.content.description }}</div>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<router-link :to="'content/' + props?.content?.id">
|
||||
<v-card-actions>
|
||||
<router-link :to="'content/' + content.id">
|
||||
<div class="bg-blue-500 rounded-lg py-1 px-2">Plus ...</div>
|
||||
</router-link>
|
||||
</div>
|
||||
</v-card-actions>
|
||||
|
||||
</div>
|
||||
</v-card>
|
||||
|
||||
<div>
|
||||
|
||||
@@ -58,28 +99,4 @@
|
||||
|
||||
</div>
|
||||
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
|
||||
import {computed, reactive} from 'vue';
|
||||
import {time_ago} from "@/internal_time_ago.js";
|
||||
import MessageList from "@/views/messages/MessageList.vue";
|
||||
import PostMessage from "@/views/messages/PostMessage.vue";
|
||||
|
||||
const isHttpUrl = computed(() => props.content?.uri?.startsWith('http'))
|
||||
|
||||
const props = defineProps({
|
||||
content: {
|
||||
type: Object,
|
||||
required: true,
|
||||
}
|
||||
});
|
||||
|
||||
const messages = reactive([]);
|
||||
|
||||
function addMessage(newMessage) {
|
||||
messages.unshift(newMessage)
|
||||
}
|
||||
|
||||
</script>
|
||||
</template>
|
||||
@@ -48,7 +48,6 @@ async function load({done}) {
|
||||
let uri = `/api/contents/user/${props.creatorId}?page_size=${page_size}`
|
||||
if (last_id !== null) uri = uri + `&last_id=${last_id}`
|
||||
|
||||
console.log(`Fetching content at: ${uri}`)
|
||||
const response = await client.get(uri)
|
||||
|
||||
if (response.status >= 200 && response.status < 300) {
|
||||
|
||||
@@ -1,72 +1,8 @@
|
||||
<template>
|
||||
|
||||
<div v-if="creator.id === userStore.getCurrentUser().id">
|
||||
|
||||
<button
|
||||
class="flex items-center text-white transform transition-transform duration-200 hover:text-gray-300 hover:scale-125 px-4"
|
||||
@click="isDialogActive = true">
|
||||
<v-icon style="font-size: 35px; height: 35px; width: 55px;">mdi-text-box-plus-outline</v-icon>
|
||||
</button>
|
||||
|
||||
<v-dialog v-model="isDialogActive" max-width="500">
|
||||
|
||||
<v-card class="text-center rounded-xl">
|
||||
|
||||
<v-card-title class="text-white p-4 rounded-t"
|
||||
:style="{backgroundColor: creator.profileColors.menu || '#A30E79'}">
|
||||
Quicky
|
||||
</v-card-title>
|
||||
|
||||
|
||||
<v-card-text>
|
||||
|
||||
<v-text-field v-model="title"
|
||||
class="p-2"
|
||||
label="Titre"
|
||||
density="comfortable"
|
||||
variant="outlined"
|
||||
hide-details
|
||||
clearable>
|
||||
</v-text-field>
|
||||
|
||||
<v-textarea v-model="message"
|
||||
label="Écrivez votre message ici..."
|
||||
class="p-2"
|
||||
density="comfortable"
|
||||
variant="outlined"
|
||||
hide-details
|
||||
clearable
|
||||
outlined>
|
||||
</v-textarea>
|
||||
|
||||
<v-file-input v-model="files"
|
||||
label="Ajoutez des fichiers"
|
||||
class="p-2"
|
||||
outlined
|
||||
multiple
|
||||
dropzone
|
||||
placeholder="Glissez et déposez des fichiers ici ou cliquez pour sélectionner des fichiers"
|
||||
></v-file-input>
|
||||
|
||||
</v-card-text>
|
||||
|
||||
<v-card-actions class="justify-end">
|
||||
<v-btn color="secondary" @click="cancelPost">Annuler</v-btn>
|
||||
<v-btn color="primary" @click="publishPost">Publier</v-btn>
|
||||
</v-card-actions>
|
||||
|
||||
</v-card>
|
||||
|
||||
</v-dialog>
|
||||
|
||||
</div>
|
||||
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
<script setup>
|
||||
import {useClient} from '@/plugins/api.js';
|
||||
import {ref} from 'vue';
|
||||
import {useUserStore} from '@/stores/user.js';
|
||||
import {useUserStore} from '@/stores/userStore.js';
|
||||
import {v7} from 'uuid'
|
||||
|
||||
const props = defineProps({
|
||||
creator: {type: Object, required: true},
|
||||
@@ -75,22 +11,34 @@ const props = defineProps({
|
||||
const userStore = useUserStore();
|
||||
|
||||
const isDialogActive = ref(false);
|
||||
const title = ref('');
|
||||
const message = ref('');
|
||||
const files = ref([]);
|
||||
|
||||
const client = useClient();
|
||||
const title = ref('');
|
||||
const message = ref('');
|
||||
const files = ref([])
|
||||
|
||||
const publishPost = async () => {
|
||||
async function publishPost() {
|
||||
|
||||
const formData = new FormData();
|
||||
formData.append('id', v7());
|
||||
formData.append('creatorId', userStore.user.id);
|
||||
formData.append('title', title.value);
|
||||
formData.append('description', message.value);
|
||||
files.value.forEach(file => {
|
||||
formData.append('files', file);
|
||||
});
|
||||
|
||||
try {
|
||||
await client.post(
|
||||
const response = await client.post(
|
||||
`/api/contents/`,
|
||||
formData,
|
||||
{
|
||||
"title": title.value,
|
||||
"description": message.value
|
||||
headers: {
|
||||
'Content-Type': 'multipart/form-data',
|
||||
}
|
||||
})
|
||||
|
||||
console.log('Files uploaded successfully:', response.data);
|
||||
closeDialog()
|
||||
} catch (error) {
|
||||
console.error(error)
|
||||
@@ -110,3 +58,69 @@ const closeDialog = () => {
|
||||
|
||||
</script>
|
||||
|
||||
<template>
|
||||
|
||||
<button
|
||||
v-if="creator.id === userStore.user.id"
|
||||
class="flex items-center text-white transform transition-transform duration-200 hover:text-gray-300 hover:scale-125 px-4"
|
||||
@click="isDialogActive = true">
|
||||
<v-icon style="font-size: 35px; height: 35px; width: 55px;">mdi-text-box-plus-outline</v-icon>
|
||||
</button>
|
||||
|
||||
<v-dialog v-model="isDialogActive"
|
||||
max-width="500">
|
||||
|
||||
<v-form>
|
||||
|
||||
<v-card class="text-center rounded-xl">
|
||||
|
||||
<v-card-title class="text-white p-4 rounded-t"
|
||||
:style="{backgroundColor: creator.profileColors.menu || '#A30E79'}">
|
||||
Quicky
|
||||
</v-card-title>
|
||||
|
||||
<v-card-text>
|
||||
|
||||
<v-text-field v-model="title"
|
||||
class="p-2"
|
||||
label="Titre"
|
||||
density="comfortable"
|
||||
variant="outlined"
|
||||
hide-details
|
||||
clearable
|
||||
></v-text-field>
|
||||
|
||||
<v-textarea v-model="message"
|
||||
label="Écrivez votre message ici..."
|
||||
class="p-2"
|
||||
density="comfortable"
|
||||
variant="outlined"
|
||||
hide-details
|
||||
clearable
|
||||
outlined
|
||||
></v-textarea>
|
||||
|
||||
<v-file-input v-model="files"
|
||||
label="Ajoutez des fichiers"
|
||||
class="p-2"
|
||||
outlined
|
||||
multiple
|
||||
dropzone
|
||||
placeholder="Glissez et déposez des fichiers ici ou cliquez pour sélectionner des fichiers"
|
||||
></v-file-input>
|
||||
|
||||
</v-card-text>
|
||||
|
||||
<v-card-actions class="justify-end">
|
||||
<v-btn color="secondary" @click="cancelPost">Annuler</v-btn>
|
||||
<v-btn color="primary" @click="publishPost">Publier</v-btn>
|
||||
</v-card-actions>
|
||||
|
||||
</v-card>
|
||||
|
||||
</v-form>
|
||||
|
||||
</v-dialog>
|
||||
|
||||
</template>
|
||||
|
||||
|
||||
31
src/views/contents/ImageViewer.vue
Normal file
31
src/views/contents/ImageViewer.vue
Normal file
@@ -0,0 +1,31 @@
|
||||
<template>
|
||||
<div class="image-container">
|
||||
<img :src="src" alt="Image" class="image" />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
const props = defineProps({
|
||||
src: {
|
||||
type: String,
|
||||
required: true,
|
||||
},
|
||||
});
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.image-container {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.image {
|
||||
max-width: 100%;
|
||||
max-height: 100%;
|
||||
object-fit: contain;
|
||||
}
|
||||
</style>
|
||||
26
src/views/contents/YoutubePlayer.vue
Normal file
26
src/views/contents/YoutubePlayer.vue
Normal file
@@ -0,0 +1,26 @@
|
||||
<template>
|
||||
<iframe
|
||||
:src="src"
|
||||
title="YouTube video player"
|
||||
allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share"
|
||||
referrerpolicy="strict-origin-when-cross-origin"
|
||||
allowfullscreen
|
||||
></iframe>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
const props = defineProps({
|
||||
src: {
|
||||
type: String,
|
||||
required: true,
|
||||
},
|
||||
});
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
iframe {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
border: 0;
|
||||
}
|
||||
</style>
|
||||
@@ -86,10 +86,9 @@
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import MyUserModel from "@/models/myUserModel.js";
|
||||
|
||||
const props = defineProps({
|
||||
user: {type: MyUserModel},
|
||||
user
|
||||
});
|
||||
|
||||
const dateRule = value => {
|
||||
|
||||
@@ -15,7 +15,8 @@
|
||||
class="text-white text-2xl transform transition-transform duration-200 hover:scale-125 hover:text-blue-500">
|
||||
<v-icon v-if="socialNetwork.icon.includes('mdi')">{{ socialNetwork.icon }}</v-icon>
|
||||
<img v-if="socialNetwork.icon.includes('tiktok')" :src="socialNetwork.icon" class="w-9 h-9" alt="Tiktok">
|
||||
<img v-if="socialNetwork.icon.includes('websiteIcon')" :src="socialNetwork.icon" class="w-9 h-9" alt="Website">
|
||||
<img v-if="socialNetwork.icon.includes('websiteIcon')" :src="socialNetwork.icon" class="w-9 h-9"
|
||||
alt="Website">
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
@@ -91,29 +92,29 @@ const props = defineProps({
|
||||
|
||||
|
||||
function GetActiveSocialNetworkUrls() {
|
||||
|
||||
const socialNetworks = [];
|
||||
const userSocialNetworks = props.creator.socialNetworks;
|
||||
if (userSocialNetworks.facebookUrl !== '') {
|
||||
if (userSocialNetworks.facebookUrl !== null) {
|
||||
socialNetworks.push({icon: "mdi-facebook", url: props.creator.socialNetworks.facebookUrl})
|
||||
}
|
||||
if (userSocialNetworks.facebookUrl !== '') {
|
||||
if (userSocialNetworks.xUrl !== null) {
|
||||
socialNetworks.push({icon: "mdi-twitter", url: props.creator.socialNetworks.xUrl})
|
||||
}
|
||||
if (userSocialNetworks.instagramUrl !== '') {
|
||||
if (userSocialNetworks.instagramUrl !== null) {
|
||||
socialNetworks.push({icon: "mdi-instagram", url: props.creator.socialNetworks.instagramUrl})
|
||||
}
|
||||
if (userSocialNetworks.tiktokUrl !== '') {
|
||||
if (userSocialNetworks.tikTokUrl !== null) {
|
||||
socialNetworks.push({
|
||||
icon: "/images/hutopymedia/icons/white/tiktokwhite.png",
|
||||
icon: '/images/externals/tiktok-white.png',
|
||||
url: props.creator.socialNetworks.tikTokUrl
|
||||
})
|
||||
}
|
||||
if (userSocialNetworks.youtubeUrl !== '') {
|
||||
socialNetworks.push({icon: "mdi-youtube", url: props.creator.socialNetworks.youtubeUrl})
|
||||
if (userSocialNetworks.youtubeUrl !== null) {
|
||||
socialNetworks.push({icon: 'mdi-youtube', url: props.creator.socialNetworks.youtubeUrl})
|
||||
}
|
||||
if (userSocialNetworks.yourWebsiteUrl !== ''){
|
||||
const websiteIconWithBackup = props.creator.storedDataUrls.websiteIconUrl === '' ? "mdi-web" : props.creator.storedDataUrls.websiteIconUrl;
|
||||
socialNetworks.push({icon: websiteIconWithBackup, url: props.creator.socialNetworks.yourWebsiteUrl})
|
||||
if (userSocialNetworks.websiteUrl !== null) {
|
||||
socialNetworks.push({icon: 'mdi-web', url: props.creator.socialNetworks.websiteUrl})
|
||||
}
|
||||
|
||||
return socialNetworks;
|
||||
|
||||
@@ -1,50 +0,0 @@
|
||||
<template>
|
||||
|
||||
<div class="mt-28">
|
||||
|
||||
<div class="max-w-md mx-auto bg-white rounded-xl shadow-md overflow-hidden md:max-w-4xl mb-4">
|
||||
<div class="md:flex">
|
||||
<div class="md:flex-shrink-0">
|
||||
<img class="h-48 w-full object-cover md:h-full md:w-48" src="/images/usersmedia/HutopyProfile/banners/banner01.png" alt="Image">
|
||||
</div>
|
||||
<div class="p-8">
|
||||
<div class="uppercase tracking-wide text-sm text-indigo-500 font-semibold">Nom du réseau social</div>
|
||||
<p class="mt-2 text-gray-500">Lorem ipsum dolor sit amet, consectetur adipiscing elit. Integer nec odio. Praesent libero. Sed cursus ante dapibus diam.</p>
|
||||
<p class="mt-2 text-gray-500">Sed nisi. Nulla quis sem at nibh elementum imperdiet. Duis sagittis ipsum.</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="max-w-md mx-auto bg-white rounded-xl shadow-md overflow-hidden md:max-w-4xl mb-4">
|
||||
<div class="md:flex">
|
||||
<div class="md:flex-shrink-0">
|
||||
<img class="h-48 w-full object-cover md:h-full md:w-48" src="/images/usersmedia/HutopyProfile/banners/banner01.png" alt="Image">
|
||||
</div>
|
||||
<div class="p-8">
|
||||
<div class="uppercase tracking-wide text-sm text-indigo-500 font-semibold">Nom du réseau social</div>
|
||||
<p class="mt-2 text-gray-500">Lorem ipsum dolor sit amet, consectetur adipiscing elit. Integer nec odio. Praesent libero. Sed cursus ante dapibus diam.</p>
|
||||
<p class="mt-2 text-gray-500">Sed nisi. Nulla quis sem at nibh elementum imperdiet. Duis sagittis ipsum.</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="max-w-md mx-auto bg-white rounded-xl shadow-md overflow-hidden md:max-w-4xl mb-4">
|
||||
<div class="md:flex">
|
||||
<div class="md:flex-shrink-0">
|
||||
<img class="h-48 w-full object-cover md:h-full md:w-48" src="/images/usersmedia/HutopyProfile/banners/banner01.png" alt="Image">
|
||||
</div>
|
||||
<div class="p-8">
|
||||
<div class="uppercase tracking-wide text-sm text-indigo-500 font-semibold">Nom du réseau social</div>
|
||||
<p class="mt-2 text-gray-500">Lorem ipsum dolor sit amet, consectetur adipiscing elit. Integer nec odio. Praesent libero. Sed cursus ante dapibus diam.</p>
|
||||
<p class="mt-2 text-gray-500">Sed nisi. Nulla quis sem at nibh elementum imperdiet. Duis sagittis ipsum.</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
|
||||
|
||||
</template>
|
||||
<script setup lang="ts">
|
||||
</script>
|
||||
@@ -4,7 +4,7 @@
|
||||
@click.stop
|
||||
>
|
||||
<div class="flex items-center">
|
||||
<v-app-bar-nav-icon @click.stop="toggleSidebar">
|
||||
<v-app-bar-nav-icon @click.stop="sideBarStore.toggle()">
|
||||
</v-app-bar-nav-icon>
|
||||
|
||||
<RouterLink to="/" class="hidden md:block">
|
||||
@@ -53,51 +53,52 @@
|
||||
<div class="text-center">
|
||||
<v-menu open-on-hover>
|
||||
<template v-slot:activator="{ props }">
|
||||
<v-btn variant="plain" v-bind="props" class="d-flex align-center text-capital-none">
|
||||
<span class="normal-case max-w-xs hidden md:block">
|
||||
{{ currentUser.userName }}
|
||||
</span>
|
||||
<div v-bind="props" class="flex align-center font-sans py-1 px-4 rounded-lg hover:bg-gray-100">
|
||||
<span class="max-w-xs hidden md:block">
|
||||
{{ userStore.alias }}
|
||||
</span>
|
||||
<img
|
||||
:src="currentUser.storedDataUrls.profilePictureUrl ?? '/images/usersmedia/anonyme/profilepictures/profileAnonymeSquare.png'"
|
||||
:src="userStore.portraitUrl"
|
||||
alt="Profile Image"
|
||||
class="ml-2 rounded-full" style="width: 32px; height: 32px;">
|
||||
</v-btn>
|
||||
class="ml-2 rounded-full"
|
||||
width="32"
|
||||
height="32">
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<v-list min-width="200px" class=" align-center mt-3 left-3">
|
||||
|
||||
<div v-if="currentUser.userName === 'Anonyme'">
|
||||
<template v-if="!authStore.isAuthenticated">
|
||||
<v-list-item class="nav-button">
|
||||
<v-list-item-title>
|
||||
<v-btn to="/login" class="w-100 " variant="plain">Connexion</v-btn>
|
||||
</v-list-item-title>
|
||||
</v-list-item>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<div v-if="currentUser.userName !== 'Anonyme'">
|
||||
|
||||
<v-list-item v-if="currentUser.creatorAlias !== null" class="nav-button">
|
||||
<router-link :to="`/@${currentUser.creatorAlias}`">
|
||||
<v-btn class="w-100 " variant="plain">@{{ currentUser.creatorAlias }}</v-btn>
|
||||
<template v-else>
|
||||
<v-list-item v-if="userStore.creator" class="nav-button">
|
||||
<router-link :to="`/@${userStore.creator.name}`">
|
||||
<v-btn class="w-100 " variant="plain">@{{ userStore.creator.name }}</v-btn>
|
||||
</router-link>
|
||||
</v-list-item>
|
||||
|
||||
<v-list-item class="nav-button">
|
||||
<v-list-item-title>
|
||||
<v-btn to="/profile" class="w-100 " variant="plain">Mon profil</v-btn>
|
||||
<v-btn to="/profile" class="w-100" variant="plain">Mon profil</v-btn>
|
||||
</v-list-item-title>
|
||||
</v-list-item>
|
||||
<v-list-item class="nav-button">
|
||||
<v-list-item-title>
|
||||
<v-btn to="/wallet" class="w-100 " variant="plain"> Portefeuille</v-btn>
|
||||
<v-btn to="/wallet" class="w-100" variant="plain">Portefeuille</v-btn>
|
||||
</v-list-item-title>
|
||||
</v-list-item>
|
||||
<v-list-item class="nav-button">
|
||||
<v-list-item-title>
|
||||
<v-btn @click="logout" to="/wallet" class="w-100 " variant="plain"> Déconnexion</v-btn>
|
||||
<v-btn @click="authStore.logout" class="w-100" variant="plain">Déconnexion</v-btn>
|
||||
</v-list-item-title>
|
||||
</v-list-item>
|
||||
</div>
|
||||
</template>
|
||||
</v-list>
|
||||
</v-menu>
|
||||
</div>
|
||||
@@ -108,21 +109,19 @@
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import {ref, onBeforeUnmount, onBeforeMount, watch, reactive} from "vue";
|
||||
import {eventBus} from '@/eventBus.js';
|
||||
import {ref, onBeforeUnmount, onBeforeMount, watch} from "vue";
|
||||
import {useRouter} from 'vue-router';
|
||||
import {useUserStore} from "@/stores/user.js";
|
||||
import MyUserModel from "@/models/myUserModel.js";
|
||||
import {useSideBarStore} from '@/stores/sideBarStore.js';
|
||||
import {useUserStore} from "@/stores/userStore.js";
|
||||
import {useAuthStore} from "@/stores/authStore.js";
|
||||
|
||||
const authStore = useAuthStore()
|
||||
const userStore = useUserStore()
|
||||
const sideBarStore = useSideBarStore()
|
||||
|
||||
const router = useRouter();
|
||||
const searchQuery = ref("");
|
||||
const showSearch = ref(false);
|
||||
let currentUser = reactive(MyUserModel.getDefaultUser());
|
||||
const userStore = useUserStore();
|
||||
|
||||
const toggleSidebar = () => {
|
||||
eventBus.value.toggleSidebar();
|
||||
};
|
||||
|
||||
const onSearch = () => {
|
||||
const query = searchQuery.value.trim();
|
||||
@@ -148,33 +147,14 @@ const handleClickOutside = (event) => {
|
||||
}
|
||||
};
|
||||
|
||||
const navigateToMessages = () => {
|
||||
router.push('/messages');
|
||||
};
|
||||
|
||||
const logout = () => {
|
||||
localStorage.removeItem('jwt');
|
||||
window.location.reload();
|
||||
};
|
||||
|
||||
onBeforeMount(() => {
|
||||
currentUser = userStore.getCurrentUser();
|
||||
document.addEventListener('click', handleClickOutside);
|
||||
document.addEventListener('click', handleClickOutside)
|
||||
});
|
||||
|
||||
// Watch the user state to get it again if needed.
|
||||
watch(
|
||||
() => userStore.hasChanged,
|
||||
() => {
|
||||
currentUser = userStore.getCurrentUser();
|
||||
const timestamp = new Date().getTime();
|
||||
currentUser.storedDataUrls.profilePictureUrl = `${currentUser.storedDataUrls.profilePictureUrl}?t=${timestamp}`;
|
||||
}
|
||||
);
|
||||
|
||||
onBeforeUnmount(() => {
|
||||
document.removeEventListener('click', handleClickOutside);
|
||||
});
|
||||
|
||||
</script>
|
||||
|
||||
|
||||
|
||||
@@ -68,9 +68,8 @@
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import {ref, onBeforeMount, onMounted} from "vue";
|
||||
import {useUserStore} from "@/stores/user.js";
|
||||
import {useClient} from "@/plugins/api.js";
|
||||
import {ref, onMounted} from "vue";
|
||||
import {useUserStore} from "@/stores/userStore.js";
|
||||
import SizeIndicator from "@/views/tools/SizeIndicator.vue";
|
||||
import ManageAccount from "@/views/Profile/ManageAccount.vue";
|
||||
import PageInformations from "@/views/Profile/PageInformations.vue";
|
||||
@@ -78,21 +77,12 @@ import PersonnalInfo from "@/views/Profile/PersonnalInfo.vue";
|
||||
import AccountSecurity from "@/views/Profile/AccountSecurity.vue";
|
||||
|
||||
const userStore = useUserStore();
|
||||
const client = useClient();
|
||||
|
||||
const currentUser = ref(null);
|
||||
const currentComponent = ref('PageInformations'); // Default component
|
||||
const isDown = ref(false);
|
||||
const startX = ref(0);
|
||||
const scrollLeft = ref(0);
|
||||
|
||||
onBeforeMount(() => {
|
||||
try {
|
||||
currentUser.value = userStore.getCurrentUser();
|
||||
} catch (error) {
|
||||
console.log("User not logged")
|
||||
}
|
||||
});
|
||||
|
||||
onMounted(() => {
|
||||
const slider = document.querySelector('.custom-scroll');
|
||||
@@ -163,9 +153,6 @@ const scrollRightFunc = () => {
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.save-btn {
|
||||
z-index: 10;
|
||||
}
|
||||
|
||||
.custom-scroll {
|
||||
-ms-overflow-style: none; /* Internet Explorer 10+ */
|
||||
@@ -176,8 +163,4 @@ const scrollRightFunc = () => {
|
||||
display: none; /* Safari and Chrome */
|
||||
}
|
||||
|
||||
.active {
|
||||
cursor: grabbing;
|
||||
cursor: -webkit-grabbing;
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -86,14 +86,13 @@
|
||||
|
||||
<script setup>
|
||||
import {ref} from 'vue';
|
||||
import MyUserModel from "@/models/myUserModel.js";
|
||||
|
||||
const emit = defineEmits(["updateProfilePicture", "updateBannerPicture"]);
|
||||
const fallbackProfilePictureUrl = '/images/usersmedia/HutopyProfile/profilepictures/profileHutopyProfile01.png';
|
||||
const fallbackBannerPictureUrl = '/images/usersmedia/HutopyProfile/banners/banner01.png';
|
||||
|
||||
const props = defineProps({
|
||||
user: {type: MyUserModel},
|
||||
user: {}
|
||||
});
|
||||
|
||||
const bannerImageUrl = ref(props.user.storedDataUrls.bannerPictureUrl);
|
||||
|
||||
@@ -1,87 +0,0 @@
|
||||
<template>
|
||||
<div ref="container">
|
||||
<div class="text-center text-2xl py-4 border-t-4">Liens des réseaux sociaux et de votre site</div>
|
||||
<div class="px-5 py-2 flex flex-col space-y-4">
|
||||
<div class="flex justify-between items-center mb-2">
|
||||
<label class="text-lg">Instagram</label>
|
||||
</div>
|
||||
<v-text-field v-model="props.user.socialNetworks.instagramUrl" label="Instagram"
|
||||
variant="outlined"></v-text-field>
|
||||
|
||||
<div class="flex justify-between items-center mb-2">
|
||||
<label class="text-lg">TikTok</label>
|
||||
</div>
|
||||
<v-text-field v-model="props.user.socialNetworks.tikTokUrl" label="TikTok" variant="outlined"></v-text-field>
|
||||
|
||||
<div class="flex justify-between items-center mb-2">
|
||||
<label class="text-lg">Facebook</label>
|
||||
</div>
|
||||
<v-text-field v-model="props.user.socialNetworks.facebookUrl" label="Facebook" variant="outlined"></v-text-field>
|
||||
|
||||
<div class="flex justify-between items-center mb-2">
|
||||
<label class="text-lg">X</label>
|
||||
</div>
|
||||
<v-text-field v-model="props.user.socialNetworks.xUrl" label="X" variant="outlined"></v-text-field>
|
||||
|
||||
<div class="flex justify-between items-center mb-2">
|
||||
<label class="text-lg">LinkedIn</label>
|
||||
</div>
|
||||
<v-text-field v-model="props.user.socialNetworks.linkedInUrl" label="LinkedIn" variant="outlined"></v-text-field>
|
||||
|
||||
<div class="flex justify-between items-center mb-2">
|
||||
<label class="text-lg">Site Web</label>
|
||||
</div>
|
||||
<v-text-field v-model="props.user.socialNetworks.yourWebsiteUrl" label="Site Web"
|
||||
variant="outlined"></v-text-field>
|
||||
|
||||
<div class="flex flex-col space-y-4">
|
||||
<div class="flex items-center mb-2">
|
||||
<label class="text-lg mr-4">Icône pour votre site web *svg</label>
|
||||
<v-file-input
|
||||
v-model="iconFile"
|
||||
accept=".png, .jpeg, .jpg"
|
||||
hint="png, jpeg or jpg"
|
||||
label="Téléverser une icône"
|
||||
@change="onFileChange">
|
||||
</v-file-input>
|
||||
</div>
|
||||
<div v-if="iconUrl" class="flex justify-center">
|
||||
<img :src="iconUrl" alt="Icon" class="icon-preview">
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import {ref} from 'vue';
|
||||
import MyUserModel from "@/models/myUserModel.js";
|
||||
|
||||
const props = defineProps({
|
||||
user: {type: MyUserModel},
|
||||
});
|
||||
|
||||
const emit = defineEmits(["updateWebsiteIcon"]);
|
||||
|
||||
const iconUrl = ref(props.user.storedDataUrls.websiteIconUrl);
|
||||
const iconFile = ref(null);
|
||||
|
||||
const onFileChange = (event) => {
|
||||
const file = event.target.files[0];
|
||||
if (file) {
|
||||
const reader = new FileReader();
|
||||
reader.onload = (e) => {
|
||||
iconUrl.value = e.target.result;
|
||||
};
|
||||
reader.readAsDataURL(file);
|
||||
emit("updateWebsiteIcon", file)
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.icon-preview {
|
||||
max-width: 250px;
|
||||
max-height: 250px;
|
||||
}
|
||||
</style>
|
||||
@@ -47,7 +47,7 @@
|
||||
<script async setup>
|
||||
import { onBeforeMount, ref, computed } from 'vue';
|
||||
import { useRouter } from 'vue-router';
|
||||
import {useUserStore} from "@/stores/user.js";
|
||||
import {useUserStore} from "@/stores/userStore.js";
|
||||
|
||||
const userStore = useUserStore();
|
||||
const router = useRouter();
|
||||
@@ -75,10 +75,8 @@ const transactionCount = computed(() => userTransactions.value.length);
|
||||
|
||||
onBeforeMount( () => {
|
||||
try {
|
||||
const myUser = userStore.getCurrentUser();
|
||||
|
||||
userTransactions.value = myUser.userTransactions;
|
||||
totalBalance.value = myUser.totalBalance;
|
||||
userTransactions.value = userStore.user.userTransactions;
|
||||
totalBalance.value = userStore.user.totalBalance;
|
||||
} catch (error) {
|
||||
navigateToHome();
|
||||
}
|
||||
|
||||
@@ -45,7 +45,7 @@
|
||||
</a>
|
||||
|
||||
<a href="https://www.tiktok.com/@guillaumeaime">
|
||||
<img class="socialicons invert-color" src="/images/hutopymedia/icons/white/tiktokwhite.png"
|
||||
<img class="socialicons invert-color" src="/images/externals/tiktok-white.png"
|
||||
alt="Description image 2">
|
||||
</a>
|
||||
</v-row>
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
<div class="flex flex-column">
|
||||
<div class="flex flex-row p-1 items-center">
|
||||
<div class="px-2 content-center">
|
||||
<img :src="message.profileImageUrl ?? '/images/usersmedia/anonyme/profilepictures/profileAnonymeSquare.png'"
|
||||
<img :src="message.createdByPortraitUrl ?? '/images/usersmedia/anonyme/profilepictures/profileAnonymeSquare.png'"
|
||||
alt="Profile Image"
|
||||
class="rounded-full"
|
||||
width="32px"
|
||||
|
||||
@@ -59,7 +59,6 @@ onBeforeMount(async () => {
|
||||
await load({
|
||||
page_size: 2,
|
||||
done: function (status) {
|
||||
console.log(`Loading status: ${status}`)
|
||||
}
|
||||
})
|
||||
})
|
||||
@@ -71,8 +70,7 @@ async function load({done, page_size}) {
|
||||
try {
|
||||
let uri = `/api/messages/${props.subjectId}?page_size=${page_size}`
|
||||
if (last_id !== null) uri = uri + `&last_id=${last_id}`
|
||||
|
||||
console.log(`Fetching messages at: ${uri}`)
|
||||
|
||||
const response = await client.get(uri)
|
||||
|
||||
if (response.status >= 200 && response.status < 300) {
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
|
||||
<div class="mx-2 content-center">
|
||||
|
||||
<img :src="profileUrl"
|
||||
<img :src="userStore.portraitUrl"
|
||||
alt="Profile Image"
|
||||
class="rounded-full"
|
||||
width="32px"
|
||||
@@ -35,10 +35,12 @@
|
||||
|
||||
<script setup>
|
||||
|
||||
import {useClient} from '@/plugins/api.js';
|
||||
import {ref} from 'vue';
|
||||
import {useUserStore} from "@/stores/user.js";
|
||||
import {v7} from 'uuid'
|
||||
import {useRouter} from "vue-router";
|
||||
import {useClient} from '@/plugins/api.js';
|
||||
import {useUserStore} from "@/stores/userStore.js";
|
||||
import {useAuthStore} from "@/stores/authStore.js";
|
||||
|
||||
const props = defineProps({
|
||||
subjectId: {
|
||||
@@ -51,13 +53,19 @@ const emit = defineEmits(['message-posted'])
|
||||
|
||||
const client = useClient()
|
||||
const value = ref("")
|
||||
const user = useUserStore()
|
||||
const profileUrl = ref(user.getCurrentUser().storedDataUrls.profilePictureUrl ?? '/images/usersmedia/anonyme/profilepictures/profileAnonymeSquare.png')
|
||||
const router = useRouter()
|
||||
const userStore = useUserStore()
|
||||
const authStore = useAuthStore()
|
||||
|
||||
const publish = async () => {
|
||||
|
||||
if (!authStore.isAuthenticated) {
|
||||
await router.push('/login')
|
||||
}
|
||||
|
||||
try {
|
||||
const messageId = v7()
|
||||
|
||||
await client.post(`/api/messages/`,
|
||||
{
|
||||
"id": messageId,
|
||||
@@ -65,17 +73,16 @@ const publish = async () => {
|
||||
"message": value.value
|
||||
})
|
||||
|
||||
const currentUser = user.getCurrentUser()
|
||||
emit('message-posted',
|
||||
{
|
||||
"id": messageId,
|
||||
"subjectId": props.subjectId,
|
||||
"createdBy": currentUser.id,
|
||||
"createdByName": currentUser.alias ?? `${currentUser.firstName} ${currentUser.lastName}`,
|
||||
"createdByPortraitUrl": currentUser.storedDataUrls.profilePictureUrl,
|
||||
"createdBy": userStore.user.id,
|
||||
"createdByName": userStore.alias,
|
||||
"createdByPortraitUrl": userStore.portraitUrl,
|
||||
"createdAt": new Date(Date.now()).toISOString(),
|
||||
"value": value.value,
|
||||
parentId: null,
|
||||
"parentId": null
|
||||
})
|
||||
|
||||
value.value = ''
|
||||
|
||||
Reference in New Issue
Block a user