feat: add public site pages and social login

This commit is contained in:
2026-05-04 16:13:57 -04:00
parent cd6f402d9e
commit 802668fb0b
9 changed files with 617 additions and 131 deletions

View File

@@ -45,23 +45,32 @@ export function useFacebookLogin() {
const loginWithFacebook = () => {
if (!isSdkLoaded.value) {
console.error("Facebook SDK non encore chargé !");
return Promise.reject(new Error("Facebook SDK is not loaded"));
}
return new Promise((resolve, reject) => {
window.FB.login(
async (response) => {
if (!response.authResponse) {
console.log("Connexion annulée ou échouée.");
reject(new Error("Facebook login was cancelled or failed"));
return;
}
window.FB.login(
(response) => {
if (response.authResponse) {
try {
console.log("Utilisateur connecté :", response);
const authStore = useAuthStore();
authStore.loginWithFacebook(response.authResponse);
} else {
console.log("Connexion annulée ou échouée.");
const result = await authStore.loginWithFacebook(response.authResponse);
resolve(result);
} catch (error) {
reject(error);
}
},
{
scope: "public_profile,email"
}
);
});
};
onMounted(() => {

View File

@@ -1,6 +1,15 @@
<template>
<div class="flex min-h-full w-full items-center justify-center p-4">
<div class="flex w-full max-w-[512px] flex-col gap-10">
<div class="login-page">
<div class="login-wrap">
<router-link
class="login-brand"
to="/"
>
<span class="login-brand-mark">S</span>
<span class="login-brand-text">Socialize</span>
</router-link>
<section class="login-card">
<h1 class="login-text text-center text-2xl font-bold">
{{ t('title') }}
</h1>
@@ -18,6 +27,18 @@
{{ t('continueWithGoogle') }}
</button>
</google-login>
<button
class="secondary"
type="button"
@click="handleFacebookLogin"
>
<v-icon
:icon="mdiFacebook"
class="mr-2"
/>
{{ t('continueWithFacebook') }}
</button>
</div>
<div class="my-4 flex items-center">
@@ -89,6 +110,7 @@
</div>
</div>
</v-form>
</section>
</div>
<!-- Error notification -->
@@ -105,13 +127,15 @@
import { ref } from 'vue';
import { GoogleLogin } from 'vue3-google-login';
import { useAuthStore } from '@/features/auth/stores/authStore.js';
import { useFacebookLogin } from '@/features/auth/composables/useFacebookLogin.js';
import { useI18n } from 'vue-i18n';
import { useRouter } from 'vue-router';
import { mdiEye, mdiEyeOff, mdiGoogle } from '@mdi/js';
import { mdiEye, mdiEyeOff, mdiFacebook, mdiGoogle } from '@mdi/js';
const { t } = useI18n();
const router = useRouter();
const authStore = useAuthStore();
const { loginWithFacebook } = useFacebookLogin();
const email = ref('');
const password = ref('');
@@ -149,6 +173,20 @@
}
}
async function handleFacebookLogin() {
try {
const response = await loginWithFacebook();
if (response === true) {
await router.push(props.returnUrl);
} else {
errorSnackBar.value = true;
}
} catch (error) {
console.error('Facebook login failed:', error);
errorSnackBar.value = true;
}
}
function forgotPassword() {
router.push('/forgot-password');
}
@@ -159,6 +197,41 @@
</script>
<style scoped>
.login-page {
@apply flex min-h-screen w-full items-stretch justify-center sm:items-center sm:p-4;
}
.login-wrap {
@apply flex min-h-screen w-full max-w-[512px] flex-col gap-6 sm:min-h-0;
}
.login-brand {
@apply mx-auto flex items-center gap-3 px-4 pt-6 no-underline sm:px-0 sm:pt-0;
color: #172033;
}
.login-brand-mark {
@apply flex h-11 w-11 items-center justify-center rounded-2xl text-lg font-black;
background: linear-gradient(135deg, #ff8a3d 0%, #ef4444 100%);
color: #fffaf2;
}
.login-brand-text {
@apply text-lg font-black uppercase tracking-[0.18em];
}
.login-card {
@apply flex min-h-0 w-full flex-1 flex-col justify-center gap-10 bg-white/80 px-5 py-8 sm:flex-none sm:rounded-[1.5rem] sm:border sm:p-8;
border-color: rgba(23, 32, 51, 0.08);
box-shadow: none;
}
@media (min-width: 640px) {
.login-card {
box-shadow: 0 18px 40px rgba(23, 32, 51, 0.08);
}
}
.visibility-toggle {
@apply cursor-pointer;
@apply transition-opacity duration-300;
@@ -194,7 +267,8 @@
"noAccount": "Don't have an account?",
"register": "Register",
"loginFailed": "Login failed. Please check your credentials.",
"continueWithGoogle": "Continue with Google"
"continueWithGoogle": "Continue with Google",
"continueWithFacebook": "Continue with Facebook"
},
"fr": {
"title": "Se connecter",
@@ -208,7 +282,8 @@
"noAccount": "Vous n'avez pas de compte?",
"register": "S'inscrire",
"loginFailed": "Échec de la connexion. Veuillez vérifier vos identifiants.",
"continueWithGoogle": "Continuer avec Google"
"continueWithGoogle": "Continuer avec Google",
"continueWithFacebook": "Continuer avec Facebook"
}
}
</i18n>

View File

@@ -0,0 +1,113 @@
<template>
<header class="site-menu">
<div class="site-menu-inner">
<router-link
class="site-brand"
to="/"
>
<span class="site-brand-mark">S</span>
<span class="site-brand-text">Socialize</span>
</router-link>
<nav
class="site-nav"
aria-label="Public site navigation"
>
<router-link to="/product">Product</router-link>
<router-link to="/pricing">Pricing</router-link>
<div class="site-nav-group">
<button type="button">Resources</button>
<div class="site-nav-menu">
<router-link to="/blogs">Blogs</router-link>
<router-link to="/guides">Guides</router-link>
</div>
</div>
</nav>
<router-link
class="site-login"
to="/login"
>
Login
</router-link>
</div>
</header>
</template>
<style scoped>
.site-menu {
@apply sticky top-0 z-30 w-full;
background: rgba(255, 250, 242, 0.9);
backdrop-filter: blur(18px);
border-bottom: 1px solid rgba(23, 32, 51, 0.08);
}
.site-menu-inner {
@apply mx-auto flex w-full max-w-7xl items-center justify-between gap-4 px-5 py-4 md:px-8;
}
.site-brand {
@apply flex min-w-0 items-center gap-3 no-underline;
color: #172033;
}
.site-brand-mark {
@apply flex h-10 w-10 flex-shrink-0 items-center justify-center rounded-2xl text-base font-black;
background: linear-gradient(135deg, #ff8a3d 0%, #ef4444 100%);
color: #fffaf2;
}
.site-brand-text {
@apply truncate text-lg font-black uppercase tracking-[0.18em];
}
.site-nav {
@apply hidden items-center justify-center gap-2 sm:flex;
}
.site-nav a {
@apply rounded-full px-4 py-2 text-sm font-semibold no-underline transition-colors;
color: #44516a;
}
.site-nav a:hover,
.site-nav-group:hover > button {
background: rgba(23, 32, 51, 0.06);
color: #172033;
}
.site-nav-group {
@apply relative;
}
.site-nav-group > button {
@apply rounded-full px-4 py-2 text-sm font-semibold transition-colors;
color: #44516a;
}
.site-nav-menu {
@apply invisible absolute left-0 top-[calc(100%+0.5rem)] flex min-w-36 flex-col gap-1 rounded-[1rem] border p-2 opacity-0 transition-opacity;
background: rgba(255, 255, 255, 0.98);
border-color: rgba(23, 32, 51, 0.08);
box-shadow: 0 18px 40px rgba(23, 32, 51, 0.12);
}
.site-nav-group:hover .site-nav-menu,
.site-nav-group:focus-within .site-nav-menu {
@apply visible opacity-100;
}
.site-nav-menu a {
@apply rounded-[0.75rem] px-3 py-2;
}
.site-login {
@apply flex h-10 items-center rounded-full px-4 text-sm font-bold no-underline transition-colors;
background: #172033;
color: #fffaf2;
}
.site-login:hover {
background: #0f766e;
}
</style>

View File

@@ -0,0 +1,52 @@
<script setup>
import LandingSiteMenu from '@/features/landing/components/LandingSiteMenu.vue';
</script>
<template>
<div class="public-page">
<LandingSiteMenu />
<main class="public-page-content">
<section class="public-page-panel">
<div class="eyebrow">Blogs</div>
<h1>Practical notes on content review workflows.</h1>
<p>
Articles are coming soon. This area will cover approval operations, client review habits,
revision tracking, and publication handoff.
</p>
</section>
</main>
</div>
</template>
<style scoped>
.public-page {
@apply min-h-screen w-full;
}
.public-page-content {
@apply mx-auto flex w-full max-w-7xl flex-col px-5 py-8 md:px-8 md:py-12;
}
.public-page-panel {
@apply rounded-[2rem] p-6 md:p-10;
background: rgba(255, 255, 255, 0.84);
border: 1px solid rgba(23, 32, 51, 0.08);
box-shadow: 0 18px 40px rgba(23, 32, 51, 0.06);
}
.eyebrow {
@apply text-xs font-bold uppercase tracking-[0.26em];
color: #ff8a3d;
}
h1 {
@apply mt-4 max-w-4xl text-4xl font-black leading-tight md:text-6xl;
color: #172033;
}
p {
@apply mt-5 max-w-3xl text-base leading-7 md:text-lg;
color: #44516a;
}
</style>

View File

@@ -0,0 +1,52 @@
<script setup>
import LandingSiteMenu from '@/features/landing/components/LandingSiteMenu.vue';
</script>
<template>
<div class="public-page">
<LandingSiteMenu />
<main class="public-page-content">
<section class="public-page-panel">
<div class="eyebrow">Guides</div>
<h1>Reusable guides for managing review and approval work.</h1>
<p>
Guides are coming soon. This area will collect repeatable playbooks for content intake,
review rounds, approval decisions, and delivery readiness.
</p>
</section>
</main>
</div>
</template>
<style scoped>
.public-page {
@apply min-h-screen w-full;
}
.public-page-content {
@apply mx-auto flex w-full max-w-7xl flex-col px-5 py-8 md:px-8 md:py-12;
}
.public-page-panel {
@apply rounded-[2rem] p-6 md:p-10;
background: rgba(255, 255, 255, 0.84);
border: 1px solid rgba(23, 32, 51, 0.08);
box-shadow: 0 18px 40px rgba(23, 32, 51, 0.06);
}
.eyebrow {
@apply text-xs font-bold uppercase tracking-[0.26em];
color: #ff8a3d;
}
h1 {
@apply mt-4 max-w-4xl text-4xl font-black leading-tight md:text-6xl;
color: #172033;
}
p {
@apply mt-5 max-w-3xl text-base leading-7 md:text-lg;
color: #44516a;
}
</style>

View File

@@ -1,5 +1,6 @@
<script setup>
import { computed } from 'vue';
import LandingSiteMenu from '@/features/landing/components/LandingSiteMenu.vue';
const pillars = computed(() => [
{
@@ -25,8 +26,14 @@
</script>
<template>
<div class="landing-shell">
<section class="hero-card">
<div class="landing-page">
<LandingSiteMenu />
<main class="landing-shell">
<section
id="products"
class="hero-card"
>
<div class="hero-copy">
<div class="eyebrow">Social media approval workflow</div>
<h1>Replace Drive links, scattered comments, and manual follow-up with one review system.</h1>
@@ -88,10 +95,28 @@
</div>
</div>
</section>
<section
id="pricing"
class="pricing-card"
>
<div>
<div class="eyebrow">Pricing</div>
<h2>Workspace pricing for teams that manage content approvals with clients.</h2>
</div>
<router-link to="/login">
<button class="secondary pricing-action">Open the app</button>
</router-link>
</section>
</main>
</div>
</template>
<style scoped>
.landing-page {
@apply min-h-screen w-full;
}
.landing-shell {
@apply mx-auto flex w-full max-w-7xl flex-col gap-8 px-5 py-8 md:px-8 md:py-12;
}
@@ -189,4 +214,20 @@
@apply mt-2 block text-sm leading-6;
color: #3f4d63;
}
.pricing-card {
@apply flex flex-col gap-5 rounded-[1.75rem] p-6 md:flex-row md:items-center md:justify-between md:p-8;
background: rgba(255, 255, 255, 0.84);
border: 1px solid rgba(23, 32, 51, 0.08);
box-shadow: 0 18px 40px rgba(23, 32, 51, 0.06);
}
.pricing-card h2 {
@apply mt-3 max-w-3xl text-2xl font-black leading-tight md:text-3xl;
color: #172033;
}
.pricing-action {
@apply w-full sm:w-auto;
}
</style>

View File

@@ -0,0 +1,68 @@
<script setup>
import LandingSiteMenu from '@/features/landing/components/LandingSiteMenu.vue';
</script>
<template>
<div class="public-page">
<LandingSiteMenu />
<main class="public-page-content">
<section class="public-page-panel">
<div class="eyebrow">Pricing</div>
<h1>Simple workspace pricing for teams managing client approvals.</h1>
<p>
Pricing details are coming soon. For now, Socialize is focused on the core review workflow:
workspaces, content items, assets, comments, and approval decisions.
</p>
<router-link
class="pricing-login"
to="/login"
>
Login
</router-link>
</section>
</main>
</div>
</template>
<style scoped>
.public-page {
@apply min-h-screen w-full;
}
.public-page-content {
@apply mx-auto flex w-full max-w-7xl flex-col px-5 py-8 md:px-8 md:py-12;
}
.public-page-panel {
@apply rounded-[2rem] p-6 md:p-10;
background: rgba(255, 255, 255, 0.84);
border: 1px solid rgba(23, 32, 51, 0.08);
box-shadow: 0 18px 40px rgba(23, 32, 51, 0.06);
}
.eyebrow {
@apply text-xs font-bold uppercase tracking-[0.26em];
color: #ff8a3d;
}
h1 {
@apply mt-4 max-w-4xl text-4xl font-black leading-tight md:text-6xl;
color: #172033;
}
p {
@apply mt-5 max-w-3xl text-base leading-7 md:text-lg;
color: #44516a;
}
.pricing-login {
@apply mt-8 inline-flex h-11 w-fit items-center rounded-full px-5 text-sm font-bold no-underline transition-colors;
background: #172033;
color: #fffaf2;
}
.pricing-login:hover {
background: #0f766e;
}
</style>

View File

@@ -0,0 +1,52 @@
<script setup>
import LandingSiteMenu from '@/features/landing/components/LandingSiteMenu.vue';
</script>
<template>
<div class="public-page">
<LandingSiteMenu />
<main class="public-page-content">
<section class="public-page-panel">
<div class="eyebrow">Product</div>
<h1>Social media content approval, organized around the work itself.</h1>
<p>
Socialize keeps content items, assets, revisions, comments, approval decisions, and publishing
handoff details in one workspace so teams do not have to coordinate review across scattered tools.
</p>
</section>
</main>
</div>
</template>
<style scoped>
.public-page {
@apply min-h-screen w-full;
}
.public-page-content {
@apply mx-auto flex w-full max-w-7xl flex-col px-5 py-8 md:px-8 md:py-12;
}
.public-page-panel {
@apply rounded-[2rem] p-6 md:p-10;
background: rgba(255, 255, 255, 0.84);
border: 1px solid rgba(23, 32, 51, 0.08);
box-shadow: 0 18px 40px rgba(23, 32, 51, 0.06);
}
.eyebrow {
@apply text-xs font-bold uppercase tracking-[0.26em];
color: #ff8a3d;
}
h1 {
@apply mt-4 max-w-4xl text-4xl font-black leading-tight md:text-6xl;
color: #172033;
}
p {
@apply mt-5 max-w-3xl text-base leading-7 md:text-lg;
color: #44516a;
}
</style>

View File

@@ -3,6 +3,10 @@ import { createRouter, createWebHistory } from 'vue-router';
const LoginView = () => import('@/features/auth/views/LoginView.vue');
const Landing = () => import('@/features/landing/views/Landing.vue');
const ProductPage = () => import('@/features/landing/views/ProductPage.vue');
const PricingPage = () => import('@/features/landing/views/PricingPage.vue');
const BlogsPage = () => import('@/features/landing/views/BlogsPage.vue');
const GuidesPage = () => import('@/features/landing/views/GuidesPage.vue');
const RegisterView = () => import('@/features/auth/views/RegisterView.vue');
const ForgotPasswordView = () => import('@/features/auth/views/ForgotPasswordView.vue');
const ResetPasswordView = () => import('@/features/auth/views/ResetPasswordView.vue');
@@ -32,6 +36,26 @@ const routes = [
name: 'landing',
component: Landing,
},
{
path: '/product',
name: 'product',
component: ProductPage,
},
{
path: '/pricing',
name: 'pricing',
component: PricingPage,
},
{
path: '/blogs',
name: 'blogs',
component: BlogsPage,
},
{
path: '/guides',
name: 'guides',
component: GuidesPage,
},
{
path: '/app',
redirect: { name: 'dashboard' },