maj
This commit is contained in:
2
.env
2
.env
@@ -1,5 +1,5 @@
|
||||
# Connexion locale via le Docker Compose fourni :
|
||||
DATABASE_URL="postgresql://admin:adminpassword@192.168.1.25:5432/afropreunariat_dev?schema=public"
|
||||
DATABASE_URL="postgresql://admin:adminpassword@localhost:5432/afropreunariat_dev?schema=public"
|
||||
|
||||
# Exemple avec Supabase ou Neon (si base distante)
|
||||
# DATABASE_URL="postgresql://postgres:[MOT_DE_PASSE]@db.[ID_PROJET].supabase.co:5432/postgres"
|
||||
@@ -4,8 +4,8 @@
|
||||
"dynamicRoutes": {},
|
||||
"notFoundRoutes": [],
|
||||
"preview": {
|
||||
"previewModeId": "e4f2fdc8a0f279696cd4c94409e5984e",
|
||||
"previewModeSigningKey": "be32103ec8267fdecdccfe457d2a03999c8e439d583c9ebfb243182a01ef67b7",
|
||||
"previewModeEncryptionKey": "034717334772a2081e323378fa45aabe5c8553ab7b0dcf59547cc44e534605ff"
|
||||
"previewModeId": "7b809f863d91f00050e5c8c0e4bada56",
|
||||
"previewModeSigningKey": "5d82ed0a1b256e09883f544d3ee37c35354f01b40d1dc9660026db8c9b02efd7",
|
||||
"previewModeEncryptionKey": "f7bb294866ff13b369a31ff5fb86e4f6ec887e7444d7550d13f24af3eb7c12db"
|
||||
}
|
||||
}
|
||||
@@ -1,10 +1,5 @@
|
||||
{
|
||||
"/afrolife/page": "app/afrolife/page.js",
|
||||
"/annuaire/page": "app/annuaire/page.js",
|
||||
"/api/analytics/route": "app/api/analytics/route.js",
|
||||
"/api/businesses/route": "app/api/businesses/route.js",
|
||||
"/blog/[id]/page": "app/blog/[id]/page.js",
|
||||
"/blog/page": "app/blog/page.js",
|
||||
"/login/page": "app/login/page.js",
|
||||
"/page": "app/page.js"
|
||||
}
|
||||
@@ -4,7 +4,7 @@ R.c("server/chunks/ssr/[root-of-the-server]__06f207ab._.js")
|
||||
R.c("server/chunks/ssr/a30ac_next_dist_2984d70a._.js")
|
||||
R.c("server/chunks/ssr/[externals]__7f148858._.js")
|
||||
R.c("server/chunks/ssr/a30ac_next_dist_client_components_builtin_global-error_c1b18c9b.js")
|
||||
R.c("server/chunks/ssr/Documents_00 - projet_afropreunariat_a9b9fee6._.js")
|
||||
R.c("server/chunks/ssr/[root-of-the-server]__4128d26c._.js")
|
||||
R.c("server/chunks/ssr/a30ac_next_dist_client_components_a7eb5008._.js")
|
||||
R.c("server/chunks/ssr/a30ac_next_dist_client_components_builtin_forbidden_24897189.js")
|
||||
R.c("server/chunks/ssr/a30ac_next_dist_client_components_builtin_unauthorized_d9e897c8.js")
|
||||
|
||||
File diff suppressed because one or more lines are too long
@@ -4,7 +4,7 @@ R.c("server/chunks/ssr/[root-of-the-server]__9a704c34._.js")
|
||||
R.c("server/chunks/ssr/a30ac_next_dist_2984d70a._.js")
|
||||
R.c("server/chunks/ssr/[externals]__7f148858._.js")
|
||||
R.c("server/chunks/ssr/a30ac_next_dist_client_components_builtin_global-error_c1b18c9b.js")
|
||||
R.c("server/chunks/ssr/Documents_00 - projet_afropreunariat_a9b9fee6._.js")
|
||||
R.c("server/chunks/ssr/[root-of-the-server]__4128d26c._.js")
|
||||
R.c("server/chunks/ssr/a30ac_next_dist_client_components_a7eb5008._.js")
|
||||
R.c("server/chunks/ssr/a30ac_next_dist_client_components_builtin_forbidden_24897189.js")
|
||||
R.c("server/chunks/ssr/a30ac_next_dist_client_components_builtin_unauthorized_d9e897c8.js")
|
||||
|
||||
File diff suppressed because one or more lines are too long
@@ -16600,7 +16600,7 @@ var __turbopack_async_dependencies__ = __turbopack_handle_async_dependencies__([
|
||||
;
|
||||
// We inject the nextConfigOutput here so that we can use them in the route
|
||||
// module.
|
||||
const nextConfigOutput = "";
|
||||
const nextConfigOutput = "standalone";
|
||||
const routeModule = new __TURBOPACK__imported__module__$5b$project$5d2f$Documents$2f$00__$2d$__projet$2f$afropreunariat$2f$node_modules$2f$next$2f$dist$2f$esm$2f$server$2f$route$2d$modules$2f$app$2d$route$2f$module$2e$compiled$2e$js__$5b$app$2d$route$5d$__$28$ecmascript$29$__["AppRouteRouteModule"]({
|
||||
definition: {
|
||||
kind: __TURBOPACK__imported__module__$5b$project$5d2f$Documents$2f$00__$2d$__projet$2f$afropreunariat$2f$node_modules$2f$next$2f$dist$2f$esm$2f$server$2f$route$2d$kind$2e$js__$5b$app$2d$route$5d$__$28$ecmascript$29$__["RouteKind"].APP_ROUTE,
|
||||
|
||||
@@ -6177,7 +6177,7 @@ var EntryStatus = /*#__PURE__*/ function(EntryStatus) {
|
||||
EntryStatus[EntryStatus["Rejected"] = 3] = "Rejected";
|
||||
return EntryStatus;
|
||||
}({});
|
||||
const isOutputExportMode = ("TURBOPACK compile-time value", "development") === 'production' && ("TURBOPACK compile-time value", void 0) === 'export';
|
||||
const isOutputExportMode = ("TURBOPACK compile-time value", "development") === 'production' && ("TURBOPACK compile-time value", "standalone") === 'export';
|
||||
const MetadataOnlyRequestTree = [
|
||||
'',
|
||||
{},
|
||||
|
||||
File diff suppressed because one or more lines are too long
@@ -1 +1 @@
|
||||
self.__RSC_SERVER_MANIFEST="{\n \"node\": {},\n \"edge\": {},\n \"encryptionKey\": \"nzqFbYTCPnKfhYIIKDYG+xnFLI0t6Og2KV+8qBSJfGk=\"\n}"
|
||||
self.__RSC_SERVER_MANIFEST="{\n \"node\": {},\n \"edge\": {},\n \"encryptionKey\": \"UBny3v2My8FSswF+XKlwEFB68MjTsPZoNJbBJmHj6O8=\"\n}"
|
||||
@@ -1,5 +1,5 @@
|
||||
{
|
||||
"node": {},
|
||||
"edge": {},
|
||||
"encryptionKey": "nzqFbYTCPnKfhYIIKDYG+xnFLI0t6Og2KV+8qBSJfGk="
|
||||
"encryptionKey": "UBny3v2My8FSswF+XKlwEFB68MjTsPZoNJbBJmHj6O8="
|
||||
}
|
||||
@@ -66,6 +66,7 @@
|
||||
--tw-backdrop-saturate: initial;
|
||||
--tw-backdrop-sepia: initial;
|
||||
--tw-duration: initial;
|
||||
--tw-ease: initial;
|
||||
--tw-content: "";
|
||||
}
|
||||
}
|
||||
@@ -166,6 +167,7 @@
|
||||
--container-2xl: 42rem;
|
||||
--container-3xl: 48rem;
|
||||
--container-4xl: 56rem;
|
||||
--container-5xl: 64rem;
|
||||
--container-6xl: 72rem;
|
||||
--container-7xl: 80rem;
|
||||
--text-xs: .75rem;
|
||||
@@ -204,6 +206,7 @@
|
||||
--radius-xl: .75rem;
|
||||
--radius-2xl: 1rem;
|
||||
--radius-3xl: 1.5rem;
|
||||
--ease-in-out: cubic-bezier(.4, 0, .2, 1);
|
||||
--animate-spin: spin 1s linear infinite;
|
||||
--animate-ping: ping 1s cubic-bezier(0, 0, .2, 1) infinite;
|
||||
--animate-pulse: pulse 2s cubic-bezier(.4, 0, .6, 1) infinite;
|
||||
@@ -570,6 +573,10 @@
|
||||
@layer components;
|
||||
|
||||
@layer utilities {
|
||||
.pointer-events-auto {
|
||||
pointer-events: auto;
|
||||
}
|
||||
|
||||
.pointer-events-none {
|
||||
pointer-events: none;
|
||||
}
|
||||
@@ -654,6 +661,10 @@
|
||||
top: calc(var(--spacing) * 3);
|
||||
}
|
||||
|
||||
.top-3\.5 {
|
||||
top: calc(var(--spacing) * 3.5);
|
||||
}
|
||||
|
||||
.top-4 {
|
||||
top: calc(var(--spacing) * 4);
|
||||
}
|
||||
@@ -730,6 +741,10 @@
|
||||
z-index: 50;
|
||||
}
|
||||
|
||||
.z-\[100\] {
|
||||
z-index: 100;
|
||||
}
|
||||
|
||||
.container {
|
||||
width: 100%;
|
||||
}
|
||||
@@ -1892,6 +1907,10 @@
|
||||
max-width: var(--container-4xl);
|
||||
}
|
||||
|
||||
.max-w-5xl {
|
||||
max-width: var(--container-5xl);
|
||||
}
|
||||
|
||||
.max-w-6xl {
|
||||
max-width: var(--container-6xl);
|
||||
}
|
||||
@@ -2376,6 +2395,10 @@
|
||||
border-color: var(--color-gray-300);
|
||||
}
|
||||
|
||||
.border-gray-700 {
|
||||
border-color: var(--color-gray-700);
|
||||
}
|
||||
|
||||
.border-gray-800 {
|
||||
border-color: var(--color-gray-800);
|
||||
}
|
||||
@@ -2608,6 +2631,16 @@
|
||||
background-color: var(--color-brand-500);
|
||||
}
|
||||
|
||||
.bg-brand-500\/10 {
|
||||
background-color: #f973161a;
|
||||
}
|
||||
|
||||
@supports (color: color-mix(in lab, red, red)) {
|
||||
.bg-brand-500\/10 {
|
||||
background-color: color-mix(in oklab, var(--color-brand-500) 10%, transparent);
|
||||
}
|
||||
}
|
||||
|
||||
.bg-brand-600 {
|
||||
background-color: var(--color-brand-600);
|
||||
}
|
||||
@@ -2880,6 +2913,10 @@
|
||||
}
|
||||
}
|
||||
|
||||
.bg-slate-950 {
|
||||
background-color: var(--color-slate-950);
|
||||
}
|
||||
|
||||
.bg-slate-950\/20 {
|
||||
background-color: #02061833;
|
||||
}
|
||||
@@ -3098,6 +3135,10 @@
|
||||
padding: calc(var(--spacing) * 10);
|
||||
}
|
||||
|
||||
.p-20 {
|
||||
padding: calc(var(--spacing) * 20);
|
||||
}
|
||||
|
||||
.px-1 {
|
||||
padding-inline: calc(var(--spacing) * 1);
|
||||
}
|
||||
@@ -3118,6 +3159,10 @@
|
||||
padding-inline: calc(var(--spacing) * 4);
|
||||
}
|
||||
|
||||
.px-5 {
|
||||
padding-inline: calc(var(--spacing) * 5);
|
||||
}
|
||||
|
||||
.px-6 {
|
||||
padding-inline: calc(var(--spacing) * 6);
|
||||
}
|
||||
@@ -3530,6 +3575,10 @@
|
||||
color: var(--color-indigo-400);
|
||||
}
|
||||
|
||||
.text-indigo-500 {
|
||||
color: var(--color-indigo-500);
|
||||
}
|
||||
|
||||
.text-indigo-600 {
|
||||
color: var(--color-indigo-600);
|
||||
}
|
||||
@@ -3802,6 +3851,16 @@
|
||||
}
|
||||
}
|
||||
|
||||
.shadow-brand-500\/20 {
|
||||
--tw-shadow-color: #f9731633;
|
||||
}
|
||||
|
||||
@supports (color: color-mix(in lab, red, red)) {
|
||||
.shadow-brand-500\/20 {
|
||||
--tw-shadow-color: color-mix(in oklab, color-mix(in oklab, var(--color-brand-500) 20%, transparent) var(--tw-shadow-alpha), transparent);
|
||||
}
|
||||
}
|
||||
|
||||
.shadow-gray-200\/50 {
|
||||
--tw-shadow-color: #e5e7eb80;
|
||||
}
|
||||
@@ -3942,6 +4001,11 @@
|
||||
transition-duration: .7s;
|
||||
}
|
||||
|
||||
.ease-in-out {
|
||||
--tw-ease: var(--ease-in-out);
|
||||
transition-timing-function: var(--ease-in-out);
|
||||
}
|
||||
|
||||
.outline-none {
|
||||
--tw-outline-style: none;
|
||||
outline-style: none;
|
||||
@@ -4145,6 +4209,12 @@
|
||||
}
|
||||
}
|
||||
|
||||
@media (hover: hover) {
|
||||
.hover\:border-gray-500:hover {
|
||||
border-color: var(--color-gray-500);
|
||||
}
|
||||
}
|
||||
|
||||
@media (hover: hover) {
|
||||
.hover\:border-slate-500:hover {
|
||||
border-color: var(--color-slate-500);
|
||||
@@ -4187,6 +4257,12 @@
|
||||
}
|
||||
}
|
||||
|
||||
@media (hover: hover) {
|
||||
.hover\:bg-brand-600:hover {
|
||||
background-color: var(--color-brand-600);
|
||||
}
|
||||
}
|
||||
|
||||
@media (hover: hover) {
|
||||
.hover\:bg-brand-700:hover {
|
||||
background-color: var(--color-brand-700);
|
||||
@@ -4229,6 +4305,12 @@
|
||||
}
|
||||
}
|
||||
|
||||
@media (hover: hover) {
|
||||
.hover\:bg-indigo-700:hover {
|
||||
background-color: var(--color-indigo-700);
|
||||
}
|
||||
}
|
||||
|
||||
@media (hover: hover) {
|
||||
.hover\:bg-orange-500:hover {
|
||||
background-color: var(--color-orange-500);
|
||||
@@ -4545,6 +4627,10 @@
|
||||
cursor: not-allowed;
|
||||
}
|
||||
|
||||
.disabled\:bg-slate-700:disabled {
|
||||
background-color: var(--color-slate-700);
|
||||
}
|
||||
|
||||
.disabled\:opacity-50:disabled {
|
||||
opacity: .5;
|
||||
}
|
||||
@@ -4860,6 +4946,12 @@
|
||||
}
|
||||
}
|
||||
|
||||
@media (min-width: 48rem) {
|
||||
.md\:col-span-2 {
|
||||
grid-column: span 2 / span 2;
|
||||
}
|
||||
}
|
||||
|
||||
@media (min-width: 48rem) {
|
||||
.md\:-mt-24 {
|
||||
margin-top: calc(var(--spacing) * -24);
|
||||
@@ -4938,6 +5030,18 @@
|
||||
}
|
||||
}
|
||||
|
||||
@media (min-width: 48rem) {
|
||||
.md\:w-auto {
|
||||
width: auto;
|
||||
}
|
||||
}
|
||||
|
||||
@media (min-width: 48rem) {
|
||||
.md\:flex-none {
|
||||
flex: none;
|
||||
}
|
||||
}
|
||||
|
||||
@media (min-width: 48rem) {
|
||||
.md\:grid-cols-2 {
|
||||
grid-template-columns: repeat(2, minmax(0, 1fr));
|
||||
@@ -4988,6 +5092,12 @@
|
||||
}
|
||||
}
|
||||
|
||||
@media (min-width: 48rem) {
|
||||
.md\:p-6 {
|
||||
padding: calc(var(--spacing) * 6);
|
||||
}
|
||||
}
|
||||
|
||||
@media (min-width: 48rem) {
|
||||
.md\:p-8 {
|
||||
padding: calc(var(--spacing) * 8);
|
||||
@@ -5461,6 +5571,11 @@ body {
|
||||
inherits: false
|
||||
}
|
||||
|
||||
@property --tw-ease {
|
||||
syntax: "*";
|
||||
inherits: false
|
||||
}
|
||||
|
||||
@property --tw-content {
|
||||
syntax: "*";
|
||||
inherits: false;
|
||||
|
||||
File diff suppressed because one or more lines are too long
@@ -1,7 +1,7 @@
|
||||
(globalThis.TURBOPACK_CHUNK_LISTS || (globalThis.TURBOPACK_CHUNK_LISTS = [])).push({
|
||||
script: typeof document === "object" ? document.currentScript : undefined,
|
||||
chunks: [
|
||||
"static/chunks/Documents_00 - projet_afropreunariat_8345028d._.js",
|
||||
"static/chunks/Documents_00 - projet_afropreunariat_c93222d5._.js",
|
||||
"static/chunks/Documents_00 - projet_afropreunariat_app_globals_062f25c8.css"
|
||||
],
|
||||
source: "dynamic"
|
||||
|
||||
@@ -6372,7 +6372,7 @@ var EntryStatus = /*#__PURE__*/ function(EntryStatus) {
|
||||
EntryStatus[EntryStatus["Rejected"] = 3] = "Rejected";
|
||||
return EntryStatus;
|
||||
}({});
|
||||
const isOutputExportMode = ("TURBOPACK compile-time value", "development") === 'production' && ("TURBOPACK compile-time value", void 0) === 'export';
|
||||
const isOutputExportMode = ("TURBOPACK compile-time value", "development") === 'production' && ("TURBOPACK compile-time value", "standalone") === 'export';
|
||||
const MetadataOnlyRequestTree = [
|
||||
'',
|
||||
{},
|
||||
|
||||
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
4
.next/dev/types/routes.d.ts
vendored
4
.next/dev/types/routes.d.ts
vendored
@@ -1,7 +1,7 @@
|
||||
// This file is generated automatically by Next.js
|
||||
// Do not edit this file manually
|
||||
|
||||
type AppRoutes = "/" | "/afrolife" | "/afrolife/[id]" | "/annuaire" | "/annuaire/[id]" | "/blog" | "/blog/[id]" | "/dashboard" | "/login" | "/register" | "/subscription" | "/suspended"
|
||||
type AppRoutes = "/" | "/afrolife" | "/afrolife/[id]" | "/annuaire" | "/annuaire/[id]" | "/blog" | "/blog/[id]" | "/cgu" | "/cgv" | "/dashboard" | "/login" | "/register" | "/subscription" | "/suspended"
|
||||
type AppRouteHandlerRoutes = "/api/admin/reports" | "/api/admin/reports/[id]" | "/api/analytics" | "/api/analytics/businesses/[id]" | "/api/auth/login" | "/api/auth/register" | "/api/blog" | "/api/blog/[id]" | "/api/businesses" | "/api/businesses/[id]" | "/api/businesses/[id]/view" | "/api/businesses/me" | "/api/conversations" | "/api/conversations/[id]" | "/api/conversations/[id]/archive" | "/api/gemini" | "/api/interviews" | "/api/interviews/[id]" | "/api/messages" | "/api/messages/[id]/report" | "/api/messages/unread" | "/api/offers" | "/api/offers/[id]" | "/api/users" | "/api/users/me" | "/api/users/profile"
|
||||
type PageRoutes = never
|
||||
type LayoutRoutes = "/"
|
||||
@@ -44,6 +44,8 @@ interface ParamMap {
|
||||
"/api/users/profile": {}
|
||||
"/blog": {}
|
||||
"/blog/[id]": { "id": string; }
|
||||
"/cgu": {}
|
||||
"/cgv": {}
|
||||
"/dashboard": {}
|
||||
"/login": {}
|
||||
"/register": {}
|
||||
|
||||
@@ -101,6 +101,24 @@ type RouteHandlerConfig<Route extends AppRouteHandlerRoutes = AppRouteHandlerRou
|
||||
type __Unused = __Check
|
||||
}
|
||||
|
||||
// Validate ../../../app/cgu/page.tsx
|
||||
{
|
||||
type __IsExpected<Specific extends AppPageConfig<"/cgu">> = Specific
|
||||
const handler = {} as typeof import("../../../app/cgu/page.js")
|
||||
type __Check = __IsExpected<typeof handler>
|
||||
// @ts-ignore
|
||||
type __Unused = __Check
|
||||
}
|
||||
|
||||
// Validate ../../../app/cgv/page.tsx
|
||||
{
|
||||
type __IsExpected<Specific extends AppPageConfig<"/cgv">> = Specific
|
||||
const handler = {} as typeof import("../../../app/cgv/page.js")
|
||||
type __Check = __IsExpected<typeof handler>
|
||||
// @ts-ignore
|
||||
type __Unused = __Check
|
||||
}
|
||||
|
||||
// Validate ../../../app/dashboard/page.tsx
|
||||
{
|
||||
type __IsExpected<Specific extends AppPageConfig<"/dashboard">> = Specific
|
||||
|
||||
55
Dockerfile
Normal file
55
Dockerfile
Normal file
@@ -0,0 +1,55 @@
|
||||
# Base image
|
||||
FROM node:20-alpine AS base
|
||||
|
||||
# Install dependencies only when needed
|
||||
FROM base AS deps
|
||||
RUN apk add --no-cache libc6-compat
|
||||
WORKDIR /app
|
||||
|
||||
# Install dependencies
|
||||
COPY package.json package-lock.json ./
|
||||
RUN npm ci
|
||||
|
||||
# Rebuild the source code only when needed
|
||||
FROM base AS builder
|
||||
WORKDIR /app
|
||||
COPY --from=deps /app/node_modules ./node_modules
|
||||
COPY . .
|
||||
|
||||
# Generate Prisma Client
|
||||
RUN npx prisma generate
|
||||
|
||||
# Build Next.js
|
||||
RUN npm run build
|
||||
|
||||
# Production image, copy all the files and run next
|
||||
FROM base AS runner
|
||||
WORKDIR /app
|
||||
|
||||
ENV NODE_ENV production
|
||||
# Install openssl for Prisma
|
||||
RUN apk add --no-cache openssl
|
||||
|
||||
RUN addgroup --system --gid 1001 nodejs
|
||||
RUN adduser --system --uid 1001 nextjs
|
||||
|
||||
COPY --from=builder /app/public ./public
|
||||
COPY --from=builder /app/prisma ./prisma
|
||||
|
||||
# Set the correct permission for prerender cache
|
||||
RUN mkdir .next
|
||||
RUN chown nextjs:nodejs .next
|
||||
|
||||
# Automatically leverage output traces to reduce image size
|
||||
COPY --from=builder --chown=nextjs:nodejs /app/.next/standalone ./
|
||||
COPY --from=builder --chown=nextjs:nodejs /app/.next/static ./.next/static
|
||||
|
||||
USER nextjs
|
||||
|
||||
EXPOSE 3000
|
||||
|
||||
ENV PORT 3000
|
||||
ENV HOSTNAME "0.0.0.0"
|
||||
|
||||
# Run migrations and start the server
|
||||
CMD ["sh", "-c", "npx prisma db push && node server.js"]
|
||||
55
admin/Dockerfile
Normal file
55
admin/Dockerfile
Normal file
@@ -0,0 +1,55 @@
|
||||
# Base image
|
||||
FROM node:20-alpine AS base
|
||||
|
||||
# Install dependencies only when needed
|
||||
FROM base AS deps
|
||||
RUN apk add --no-cache libc6-compat
|
||||
WORKDIR /app
|
||||
|
||||
# Install dependencies
|
||||
COPY package.json package-lock.json ./
|
||||
RUN npm ci
|
||||
|
||||
# Rebuild the source code only when needed
|
||||
FROM base AS builder
|
||||
WORKDIR /app
|
||||
COPY --from=deps /app/node_modules ./node_modules
|
||||
COPY . .
|
||||
|
||||
# Generate Prisma Client
|
||||
RUN npx prisma generate
|
||||
|
||||
# Build Next.js
|
||||
RUN npm run build
|
||||
|
||||
# Production image, copy all the files and run next
|
||||
FROM base AS runner
|
||||
WORKDIR /app
|
||||
|
||||
ENV NODE_ENV production
|
||||
# Install openssl for Prisma
|
||||
RUN apk add --no-cache openssl
|
||||
|
||||
RUN addgroup --system --gid 1001 nodejs
|
||||
RUN adduser --system --uid 1001 nextjs
|
||||
|
||||
COPY --from=builder /app/public ./public
|
||||
COPY --from=builder /app/prisma ./prisma
|
||||
|
||||
# Set the correct permission for prerender cache
|
||||
RUN mkdir .next
|
||||
RUN chown nextjs:nodejs .next
|
||||
|
||||
# Automatically leverage output traces to reduce image size
|
||||
COPY --from=builder --chown=nextjs:nodejs /app/.next/standalone ./
|
||||
COPY --from=builder --chown=nextjs:nodejs /app/.next/static ./.next/static
|
||||
|
||||
USER nextjs
|
||||
|
||||
EXPOSE 3001
|
||||
|
||||
ENV PORT 3001
|
||||
ENV HOSTNAME "0.0.0.0"
|
||||
|
||||
# Run migrations and start the server
|
||||
CMD ["sh", "-c", "npx prisma db push && node server.js"]
|
||||
@@ -2,6 +2,7 @@ import type { NextConfig } from "next";
|
||||
|
||||
const nextConfig: NextConfig = {
|
||||
/* config options here */
|
||||
output: 'standalone',
|
||||
};
|
||||
|
||||
export default nextConfig;
|
||||
|
||||
@@ -125,6 +125,21 @@ enum InterviewType {
|
||||
ARTICLE
|
||||
}
|
||||
|
||||
model SiteSetting {
|
||||
id String @id @default("singleton")
|
||||
siteName String @default("Afropreunariat")
|
||||
siteSlogan String @default("La plateforme de référence pour l'entrepreneuriat africain.")
|
||||
contactEmail String @default("support@afropreunariat.com")
|
||||
contactPhone String? @default("+225 00 00 00 00 00")
|
||||
address String? @default("Abidjan, Côte d'Ivoire")
|
||||
facebookUrl String?
|
||||
twitterUrl String?
|
||||
instagramUrl String?
|
||||
linkedinUrl String?
|
||||
footerText String? @default("© 2025 Afropreunariat. Tous droits réservés.")
|
||||
updatedAt DateTime @updatedAt
|
||||
}
|
||||
|
||||
model Interview {
|
||||
id String @id @default(uuid())
|
||||
title String
|
||||
|
||||
53
admin/src/app/actions/settings.ts
Normal file
53
admin/src/app/actions/settings.ts
Normal file
@@ -0,0 +1,53 @@
|
||||
"use server";
|
||||
|
||||
import { prisma } from '@/lib/prisma';
|
||||
import { revalidatePath } from 'next/cache';
|
||||
|
||||
export async function getSiteSettings() {
|
||||
try {
|
||||
const settings = await prisma.siteSetting.findUnique({
|
||||
where: { id: 'singleton' }
|
||||
});
|
||||
|
||||
// Default fallback values if not initialized
|
||||
const defaultSettings = {
|
||||
siteName: "Afropreunariat",
|
||||
siteSlogan: "La plateforme de référence pour l'entrepreneuriat africain.",
|
||||
contactEmail: "support@afropreunariat.com",
|
||||
contactPhone: "+225 00 00 00 00 00",
|
||||
address: "Abidjan, Côte d'Ivoire",
|
||||
facebookUrl: "",
|
||||
twitterUrl: "",
|
||||
instagramUrl: "",
|
||||
linkedinUrl: "",
|
||||
footerText: "© 2025 Afropreunariat. Tous droits réservés."
|
||||
};
|
||||
|
||||
if (!settings) return defaultSettings;
|
||||
return settings;
|
||||
} catch (error) {
|
||||
console.error("Failed to fetch settings:", error);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
export async function updateSiteSettings(data: any) {
|
||||
try {
|
||||
const settings = await prisma.siteSetting.upsert({
|
||||
where: { id: 'singleton' },
|
||||
update: data,
|
||||
create: {
|
||||
id: 'singleton',
|
||||
...data
|
||||
}
|
||||
});
|
||||
|
||||
revalidatePath('/settings');
|
||||
revalidatePath('/', 'layout'); // Revalidate all public pages because footer/header might use it
|
||||
|
||||
return { success: true, settings };
|
||||
} catch (error) {
|
||||
console.error("Failed to update settings:", error);
|
||||
return { success: false, error: "Erreur lors de la mise à jour des paramètres" };
|
||||
}
|
||||
}
|
||||
218
admin/src/app/settings/page.tsx
Normal file
218
admin/src/app/settings/page.tsx
Normal file
@@ -0,0 +1,218 @@
|
||||
"use client";
|
||||
|
||||
import React, { useState, useEffect, useTransition } from 'react';
|
||||
import { Save, Globe, Mail, Phone, MapPin, Share2, Link as LinkIcon, Loader2 } from 'lucide-react';
|
||||
import { getSiteSettings, updateSiteSettings } from '@/app/actions/settings';
|
||||
import { toast } from 'react-hot-toast';
|
||||
|
||||
export default function SettingsPage() {
|
||||
const [isPending, startTransition] = useTransition();
|
||||
const [settings, setSettings] = useState<any>(null);
|
||||
const [loading, setLoading] = useState(true);
|
||||
|
||||
useEffect(() => {
|
||||
async function loadData() {
|
||||
const data = await getSiteSettings();
|
||||
setSettings(data);
|
||||
setLoading(false);
|
||||
}
|
||||
loadData();
|
||||
}, []);
|
||||
|
||||
const handleSubmit = async (e: React.FormEvent<HTMLFormElement>) => {
|
||||
e.preventDefault();
|
||||
const formData = new FormData(e.currentTarget);
|
||||
const data = {
|
||||
siteName: formData.get('siteName'),
|
||||
siteSlogan: formData.get('siteSlogan'),
|
||||
contactEmail: formData.get('contactEmail'),
|
||||
contactPhone: formData.get('contactPhone'),
|
||||
address: formData.get('address'),
|
||||
facebookUrl: formData.get('facebookUrl'),
|
||||
twitterUrl: formData.get('twitterUrl'),
|
||||
instagramUrl: formData.get('instagramUrl'),
|
||||
linkedinUrl: formData.get('linkedinUrl'),
|
||||
footerText: formData.get('footerText'),
|
||||
};
|
||||
|
||||
startTransition(async () => {
|
||||
const result = await updateSiteSettings(data);
|
||||
if (result.success) {
|
||||
toast.success("Paramètres mis à jour avec succès");
|
||||
} else {
|
||||
toast.error("Erreur lors de la mise à jour");
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
if (loading) {
|
||||
return (
|
||||
<div className="flex items-center justify-center p-20">
|
||||
<Loader2 className="w-8 h-8 animate-spin text-indigo-500" />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="p-8 max-w-5xl mx-auto">
|
||||
<div className="flex items-center justify-between mb-8">
|
||||
<div>
|
||||
<h1 className="text-2xl font-bold text-white">Configuration du Site</h1>
|
||||
<p className="text-slate-400">Gérez les informations globales et les coordonnées de contact du site principal.</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<form onSubmit={handleSubmit} className="space-y-6">
|
||||
{/* Section Générale */}
|
||||
<div className="bg-slate-900/50 border border-slate-700 rounded-2xl overflow-hidden">
|
||||
<div className="p-4 bg-slate-800/50 border-b border-slate-700 flex items-center gap-2">
|
||||
<Globe className="w-5 h-5 text-indigo-400" />
|
||||
<h2 className="font-semibold text-white">Informations Générales</h2>
|
||||
</div>
|
||||
<div className="p-6 grid grid-cols-1 md:grid-cols-2 gap-6">
|
||||
<div className="space-y-2">
|
||||
<label className="text-sm font-medium text-slate-400">Nom du site</label>
|
||||
<input
|
||||
name="siteName"
|
||||
defaultValue={settings?.siteName}
|
||||
className="w-full bg-slate-950 border border-slate-700 rounded-xl p-3 text-white focus:outline-none focus:border-indigo-500"
|
||||
/>
|
||||
</div>
|
||||
<div className="space-y-2">
|
||||
<label className="text-sm font-medium text-slate-400">Slogan du site</label>
|
||||
<input
|
||||
name="siteSlogan"
|
||||
defaultValue={settings?.siteSlogan}
|
||||
className="w-full bg-slate-950 border border-slate-700 rounded-xl p-3 text-white focus:outline-none focus:border-indigo-500"
|
||||
/>
|
||||
</div>
|
||||
<div className="md:col-span-2 space-y-2">
|
||||
<label className="text-sm font-medium text-slate-400">Texte du pied de page (Footer)</label>
|
||||
<input
|
||||
name="footerText"
|
||||
defaultValue={settings?.footerText}
|
||||
className="w-full bg-slate-950 border border-slate-700 rounded-xl p-3 text-white focus:outline-none focus:border-indigo-500"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Section Contact */}
|
||||
<div className="bg-slate-900/50 border border-slate-700 rounded-2xl overflow-hidden">
|
||||
<div className="p-4 bg-slate-800/50 border-b border-slate-700 flex items-center gap-2">
|
||||
<Mail className="w-5 h-5 text-emerald-400" />
|
||||
<h2 className="font-semibold text-white">Coordonnées de Contact</h2>
|
||||
</div>
|
||||
<div className="p-6 grid grid-cols-1 md:grid-cols-2 gap-6">
|
||||
<div className="space-y-2">
|
||||
<label className="text-sm font-medium text-slate-400">Email de support</label>
|
||||
<div className="relative">
|
||||
<Mail className="absolute left-3 top-3.5 w-4 h-4 text-slate-500" />
|
||||
<input
|
||||
name="contactEmail"
|
||||
defaultValue={settings?.contactEmail}
|
||||
className="w-full bg-slate-950 border border-slate-700 rounded-xl p-3 pl-10 text-white focus:outline-none focus:border-indigo-500"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div className="space-y-2">
|
||||
<label className="text-sm font-medium text-slate-400">Téléphone de contact</label>
|
||||
<div className="relative">
|
||||
<Phone className="absolute left-3 top-3.5 w-4 h-4 text-slate-500" />
|
||||
<input
|
||||
name="contactPhone"
|
||||
defaultValue={settings?.contactPhone}
|
||||
className="w-full bg-slate-950 border border-slate-700 rounded-xl p-3 pl-10 text-white focus:outline-none focus:border-indigo-500"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div className="md:col-span-2 space-y-2">
|
||||
<label className="text-sm font-medium text-slate-400">Adresse physique</label>
|
||||
<div className="relative">
|
||||
<MapPin className="absolute left-3 top-3.5 w-4 h-4 text-slate-500" />
|
||||
<input
|
||||
name="address"
|
||||
defaultValue={settings?.address}
|
||||
className="w-full bg-slate-950 border border-slate-700 rounded-xl p-3 pl-10 text-white focus:outline-none focus:border-indigo-500"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Section Réseaux Sociaux */}
|
||||
<div className="bg-slate-900/50 border border-slate-700 rounded-2xl overflow-hidden">
|
||||
<div className="p-4 bg-slate-800/50 border-b border-slate-700 flex items-center gap-2">
|
||||
<Share2 className="w-5 h-5 text-blue-400" />
|
||||
<h2 className="font-semibold text-white">Réseaux Sociaux</h2>
|
||||
</div>
|
||||
<div className="p-6 grid grid-cols-1 md:grid-cols-2 gap-6">
|
||||
<div className="space-y-2">
|
||||
<label className="text-sm font-medium text-slate-400">Facebook URL</label>
|
||||
<div className="relative">
|
||||
<LinkIcon className="absolute left-3 top-3.5 w-4 h-4 text-slate-500" />
|
||||
<input
|
||||
name="facebookUrl"
|
||||
defaultValue={settings?.facebookUrl}
|
||||
placeholder="https://facebook.com/..."
|
||||
className="w-full bg-slate-950 border border-slate-700 rounded-xl p-3 pl-10 text-white focus:outline-none focus:border-indigo-500"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div className="space-y-2">
|
||||
<label className="text-sm font-medium text-slate-400">Twitter (X) URL</label>
|
||||
<div className="relative">
|
||||
<LinkIcon className="absolute left-3 top-3.5 w-4 h-4 text-slate-500" />
|
||||
<input
|
||||
name="twitterUrl"
|
||||
defaultValue={settings?.twitterUrl}
|
||||
placeholder="https://twitter.com/..."
|
||||
className="w-full bg-slate-950 border border-slate-700 rounded-xl p-3 pl-10 text-white focus:outline-none focus:border-indigo-500"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div className="space-y-2">
|
||||
<label className="text-sm font-medium text-slate-400">Instagram URL</label>
|
||||
<div className="relative">
|
||||
<LinkIcon className="absolute left-3 top-3.5 w-4 h-4 text-slate-500" />
|
||||
<input
|
||||
name="instagramUrl"
|
||||
defaultValue={settings?.instagramUrl}
|
||||
placeholder="https://instagram.com/..."
|
||||
className="w-full bg-slate-950 border border-slate-700 rounded-xl p-3 pl-10 text-white focus:outline-none focus:border-indigo-500"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div className="space-y-2">
|
||||
<label className="text-sm font-medium text-slate-400">LinkedIn URL</label>
|
||||
<div className="relative">
|
||||
<LinkIcon className="absolute left-3 top-3.5 w-4 h-4 text-slate-500" />
|
||||
<input
|
||||
name="linkedinUrl"
|
||||
defaultValue={settings?.linkedinUrl}
|
||||
placeholder="https://linkedin.com/in/..."
|
||||
className="w-full bg-slate-950 border border-slate-700 rounded-xl p-3 pl-10 text-white focus:outline-none focus:border-indigo-500"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="flex justify-end">
|
||||
<button
|
||||
type="submit"
|
||||
disabled={isPending}
|
||||
className="bg-indigo-600 hover:bg-indigo-700 disabled:bg-slate-700 text-white px-8 py-3 rounded-xl font-bold transition-all shadow-lg flex items-center gap-2"
|
||||
>
|
||||
{isPending ? (
|
||||
<Loader2 className="w-5 h-5 animate-spin" />
|
||||
) : (
|
||||
<Save className="w-5 h-5" />
|
||||
)}
|
||||
Enregistrer les modifications
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -10,7 +10,8 @@ import {
|
||||
LogOut,
|
||||
ShieldCheck,
|
||||
BarChart3,
|
||||
Flag
|
||||
Flag,
|
||||
Settings
|
||||
} from 'lucide-react';
|
||||
|
||||
const menuItems = [
|
||||
@@ -21,6 +22,7 @@ const menuItems = [
|
||||
{ name: 'Commentaires', icon: MessageSquare, href: '/comments' },
|
||||
{ name: 'Modération', icon: Flag, href: '/moderation' },
|
||||
{ name: 'Blog & Interviews', icon: BookOpen, href: '/blog' },
|
||||
{ name: 'Configuration', icon: Settings, href: '/settings' },
|
||||
];
|
||||
|
||||
export default function Sidebar() {
|
||||
|
||||
77
app/cgu/page.tsx
Normal file
77
app/cgu/page.tsx
Normal file
@@ -0,0 +1,77 @@
|
||||
import React from 'react';
|
||||
|
||||
export const metadata = {
|
||||
title: 'Conditions Générales d\'Utilisation - Afropreunariat',
|
||||
description: 'Consultez les conditions générales d\'utilisation de la plateforme Afropreunariat.',
|
||||
};
|
||||
|
||||
export default function CGUPage() {
|
||||
return (
|
||||
<div className="bg-white min-h-screen py-16 px-4 sm:px-6 lg:px-8">
|
||||
<div className="max-w-3xl mx-auto">
|
||||
<h1 className="text-3xl font-serif font-bold text-gray-900 mb-8 border-b pb-4">
|
||||
Conditions Générales d'Utilisation (CGU)
|
||||
</h1>
|
||||
|
||||
<div className="prose prose-orange max-w-none text-gray-600 space-y-6">
|
||||
<p className="text-sm italic text-gray-500 mb-8">Dernière mise à jour : 12 avril 2026</p>
|
||||
|
||||
<section>
|
||||
<h2 className="text-xl font-bold text-gray-900 mb-3">1. Présentation du site</h2>
|
||||
<p>
|
||||
En vertu de l'article 6 de la loi n° 2004-575 du 21 juin 2004 pour la confiance dans l'économie numérique, il est précisé aux utilisateurs du site Afropreunariat l'identité des différents intervenants dans le cadre de sa réalisation et de son suivi.
|
||||
</p>
|
||||
</section>
|
||||
|
||||
<section>
|
||||
<h2 className="text-xl font-bold text-gray-900 mb-3">2. Conditions générales d’utilisation du site et des services proposés</h2>
|
||||
<p>
|
||||
L’utilisation du site Afropreunariat implique l’acceptation pleine et entière des conditions générales d’utilisation ci-après décrites. Ces conditions d’utilisation sont susceptibles d’être modifiées ou complétées à tout moment, les utilisateurs du site sont donc invités à les consulter de manière régulière.
|
||||
</p>
|
||||
</section>
|
||||
|
||||
<section>
|
||||
<h2 className="text-xl font-bold text-gray-900 mb-3">3. Description des services fournis</h2>
|
||||
<p>
|
||||
Le site Afropreunariat a pour objet de fournir une plateforme de mise en relation entre entrepreneurs africains et clients, ainsi qu'un annuaire d'entreprises. Afropreunariat s’efforce de fournir sur le site des informations aussi précises que possible. Toutefois, il ne pourra être tenue responsable des omissions, des inexactitudes et des carences dans la mise à jour, qu’elles soient de son fait ou du fait des tiers partenaires qui lui fournissent ces informations.
|
||||
</p>
|
||||
</section>
|
||||
|
||||
<section>
|
||||
<h2 className="text-xl font-bold text-gray-900 mb-3">4. Limitations contractuelles sur les données techniques</h2>
|
||||
<p>
|
||||
Le site utilise la technologie JavaScript (Next.js). Le site Internet ne pourra être tenu responsable de dommages matériels liés à l’utilisation du site. De plus, l’utilisateur du site s’engage à accéder au site en utilisant un matériel récent, ne contenant pas de virus et avec un navigateur de dernière génération mis à jour.
|
||||
</p>
|
||||
</section>
|
||||
|
||||
<section>
|
||||
<h2 className="text-xl font-bold text-gray-900 mb-3">5. Propriété intellectuelle et contrefaçons</h2>
|
||||
<p>
|
||||
Afropreunariat est propriétaire des droits de propriété intellectuelle ou détient les droits d’usage sur tous les éléments accessibles sur le site, notamment les textes, images, graphismes, logo, icônes, sons, logiciels. Toute reproduction, représentation, modification, publication, adaptation de tout ou partie des éléments du site, quel que soit le moyen ou le procédé utilisé, est interdite, sauf autorisation écrite préalable.
|
||||
</p>
|
||||
</section>
|
||||
|
||||
<section>
|
||||
<h2 className="text-xl font-bold text-gray-900 mb-3">6. Limitations de responsabilité</h2>
|
||||
<p>
|
||||
Afropreunariat ne pourra être tenue responsable des dommages directs et indirects causés au matériel de l’utilisateur, lors de l’accès au site Afropreunariat. Un espace interactif (possibilité de poser des questions dans l’espace contact) est à la disposition des utilisateurs. Afropreunariat se réserve le droit de supprimer, sans mise en demeure préalable, tout contenu déposé dans cet espace qui contreviendrait à la législation applicable en France (incluant RGPD).
|
||||
</p>
|
||||
</section>
|
||||
|
||||
<section>
|
||||
<h2 className="text-xl font-bold text-gray-900 mb-3">7. Gestion des données personnelles</h2>
|
||||
<p>
|
||||
En France, les données personnelles sont notamment protégées par la loi n° 78-87 du 6 janvier 1978, la loi n° 2004-801 du 6 août 2004, l'article L. 226-13 du Code pénal et le Règlement Européen sur la Protection des Données (RGPD). À l'occasion de l'utilisation du site, peuvent être recueillies : l'URL des liens par l'intermédiaire desquels l'utilisateur a accédé au site, le fournisseur d'accès de l'utilisateur, l'adresse de protocole Internet (IP) de l'utilisateur.
|
||||
</p>
|
||||
</section>
|
||||
|
||||
<div className="bg-gray-50 p-6 rounded-lg border border-gray-100 mt-12">
|
||||
<p className="text-xs text-center text-gray-500 italic">
|
||||
Note : Ce document est un modèle simplifié. Pour toute activité commerciale réelle, il est fortement recommandé de consulter un avocat pour adapter ce document à votre situation spécifique.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
77
app/cgv/page.tsx
Normal file
77
app/cgv/page.tsx
Normal file
@@ -0,0 +1,77 @@
|
||||
import React from 'react';
|
||||
|
||||
export const metadata = {
|
||||
title: 'Conditions Générales de Vente - Afropreunariat',
|
||||
description: 'Consultez les conditions générales de vente des services Afropreunariat.',
|
||||
};
|
||||
|
||||
export default function CGVPage() {
|
||||
return (
|
||||
<div className="bg-white min-h-screen py-16 px-4 sm:px-6 lg:px-8">
|
||||
<div className="max-w-3xl mx-auto">
|
||||
<h1 className="text-3xl font-serif font-bold text-gray-900 mb-8 border-b pb-4">
|
||||
Conditions Générales de Vente (CGV)
|
||||
</h1>
|
||||
|
||||
<div className="prose prose-orange max-w-none text-gray-600 space-y-6">
|
||||
<p className="text-sm italic text-gray-500 mb-8">Dernière mise à jour : 12 avril 2026</p>
|
||||
|
||||
<section>
|
||||
<h2 className="text-xl font-bold text-gray-900 mb-3">1. Objet</h2>
|
||||
<p>
|
||||
Les présentes conditions générales de vente (CGV) régissent les relations contractuelles entre la plateforme Afropreunariat et tout utilisateur professionnel (l'Entrepreneur) souscrivant à des services payants sur le site.
|
||||
</p>
|
||||
</section>
|
||||
|
||||
<section>
|
||||
<h2 className="text-xl font-bold text-gray-900 mb-3">2. Description des services payants</h2>
|
||||
<p>
|
||||
Afropreunariat propose divers services payants destinés aux entrepreneurs, incluant mais ne se limitant pas à : l'inscription premium dans l'annuaire, la mise en avant de fiches entreprises, et des outils de communication avancés. Le contenu et les tarifs de ces services sont détaillés sur la page de tarification.
|
||||
</p>
|
||||
</section>
|
||||
|
||||
<section>
|
||||
<h2 className="text-xl font-bold text-gray-900 mb-3">3. Tarifs et paiement</h2>
|
||||
<p>
|
||||
Les prix de nos services sont indiqués en euros (ou monnaie locale) toutes taxes comprises. Afropreunariat se réserve le droit de modifier ses prix à tout moment, étant toutefois entendu que le prix figurant sur le site au jour de la commande sera le seul applicable à l'Entrepreneur. Le paiement est exigible immédiatement à la commande.
|
||||
</p>
|
||||
</section>
|
||||
|
||||
<section>
|
||||
<h2 className="text-xl font-bold text-gray-900 mb-3">4. Droit de rétractation</h2>
|
||||
<p>
|
||||
Conformément à l'article L221-28 du Code de la consommation, le droit de rétractation ne peut être exercé pour les services pleinement exécutés avant la fin du délai de rétractation ou dont l'exécution a commencé avec votre accord préalable exprès et renoncement exprès à votre droit de rétractation. Les abonnements numériques et services de mise en avant immédiate entrent dans ce cadre.
|
||||
</p>
|
||||
</section>
|
||||
|
||||
<section>
|
||||
<h2 className="text-xl font-bold text-gray-900 mb-3">5. Durée et résiliation</h2>
|
||||
<p>
|
||||
Les services de type "Abonnement" sont conclus pour la durée choisie lors de la souscription (mensuelle ou annuelle). Sauf mention contraire, ils sont reconduits tacitement. L'Entrepreneur peut résilier son abonnement à tout moment depuis son tableau de bord, la résiliation prenant effet à la fin de la période en cours.
|
||||
</p>
|
||||
</section>
|
||||
|
||||
<section>
|
||||
<h2 className="text-xl font-bold text-gray-900 mb-3">6. Responsabilité</h2>
|
||||
<p>
|
||||
La plateforme Afropreunariat n'est tenue que par une obligation de moyens. Elle ne saurait être tenue pour responsable des dommages résultant d'une mauvaise utilisation du service ou de l'absence de retours commerciaux suite à une mise en avant sur la plateforme.
|
||||
</p>
|
||||
</section>
|
||||
|
||||
<section>
|
||||
<h2 className="text-xl font-bold text-gray-900 mb-3">7. Règlement des litiges</h2>
|
||||
<p>
|
||||
Les présentes conditions de vente sont soumises à la loi française. En cas de litige, compétence est attribuée aux tribunaux compétents, nonobstant pluralité de défendeurs ou appel en garantie.
|
||||
</p>
|
||||
</section>
|
||||
|
||||
<div className="bg-gray-50 p-6 rounded-lg border border-gray-100 mt-12">
|
||||
<p className="text-xs text-center text-gray-500 italic">
|
||||
Note : Ce document est un modèle simplifié. Pour toute activité commerciale réelle, il est fortement recommandé de consulter un avocat pour adapter ce document à votre situation spécifique.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -4,8 +4,10 @@ import Navbar from '../components/Navbar';
|
||||
import Footer from '../components/Footer';
|
||||
import { UserProvider } from '../components/UserProvider';
|
||||
import AnalyticsTracker from '../components/AnalyticsTracker';
|
||||
import CookieBanner from '../components/CookieBanner';
|
||||
import { Metadata } from 'next';
|
||||
import { Toaster } from 'react-hot-toast';
|
||||
import { getSiteSettings } from '../lib/settings';
|
||||
import './globals.css';
|
||||
|
||||
export const metadata: Metadata = {
|
||||
@@ -13,7 +15,9 @@ export const metadata: Metadata = {
|
||||
description: 'Annuaire Afropreunariat',
|
||||
};
|
||||
|
||||
export default function RootLayout({ children }: { children: React.ReactNode }) {
|
||||
export default async function RootLayout({ children }: { children: React.ReactNode }) {
|
||||
const settings = await getSiteSettings();
|
||||
|
||||
return (
|
||||
<html lang="fr">
|
||||
<head>
|
||||
@@ -28,7 +32,8 @@ export default function RootLayout({ children }: { children: React.ReactNode })
|
||||
<main className="flex-grow">
|
||||
{children}
|
||||
</main>
|
||||
<Footer />
|
||||
<Footer settings={settings} />
|
||||
<CookieBanner />
|
||||
</UserProvider>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
74
components/CookieBanner.tsx
Normal file
74
components/CookieBanner.tsx
Normal file
@@ -0,0 +1,74 @@
|
||||
"use client";
|
||||
|
||||
import React, { useState, useEffect } from 'react';
|
||||
import Link from 'next/link';
|
||||
import { X, Cookie } from 'lucide-react';
|
||||
|
||||
const CookieBanner = () => {
|
||||
const [isVisible, setIsVisible] = useState(false);
|
||||
|
||||
useEffect(() => {
|
||||
// Check if the user has already consented
|
||||
const consent = localStorage.getItem('cookie-consent');
|
||||
if (!consent) {
|
||||
// Small delay for better UX
|
||||
const timer = setTimeout(() => {
|
||||
setIsVisible(true);
|
||||
}, 1500);
|
||||
return () => clearTimeout(timer);
|
||||
}
|
||||
}, []);
|
||||
|
||||
const handleConsent = (level: 'all' | 'essential') => {
|
||||
localStorage.setItem('cookie-consent', level);
|
||||
setIsVisible(false);
|
||||
};
|
||||
|
||||
if (!isVisible) return null;
|
||||
|
||||
return (
|
||||
<div className="fixed bottom-0 left-0 right-0 z-[100] p-4 md:p-6 pointer-events-none">
|
||||
<div className="max-w-4xl mx-auto bg-gray-900 text-white rounded-2xl shadow-2xl border border-gray-800 p-5 md:p-6 pointer-events-auto transform transition-all duration-500 ease-in-out animate-in slide-in-from-bottom-10">
|
||||
<div className="flex flex-col md:flex-row items-start md:items-center justify-between gap-6">
|
||||
<div className="flex items-start gap-4">
|
||||
<div className="bg-brand-500/10 p-2 rounded-lg shrink-0">
|
||||
<Cookie className="w-6 h-6 text-brand-500" />
|
||||
</div>
|
||||
<div>
|
||||
<h3 className="text-lg font-bold mb-1">Nous respectons votre vie privée</h3>
|
||||
<p className="text-sm text-gray-400 leading-relaxed">
|
||||
Afropreunariat utilise des cookies pour améliorer votre expérience, analyser le trafic et personnaliser le contenu.
|
||||
Certains sont essentiels, d'autres nous aident à grandir avec vous.
|
||||
Consultez nos <Link href="/cgu" className="text-brand-500 hover:underline">CGU</Link> pour en savoir plus.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="flex flex-wrap gap-3 shrink-0 w-full md:w-auto">
|
||||
<button
|
||||
onClick={() => handleConsent('essential')}
|
||||
className="flex-1 md:flex-none px-5 py-2.5 text-sm font-medium text-gray-300 hover:text-white border border-gray-700 hover:border-gray-500 rounded-xl transition-all"
|
||||
>
|
||||
Uniquement essentiels
|
||||
</button>
|
||||
<button
|
||||
onClick={() => handleConsent('all')}
|
||||
className="flex-1 md:flex-none px-5 py-2.5 text-sm font-medium bg-brand-500 hover:bg-brand-600 text-white rounded-xl shadow-lg shadow-brand-500/20 transition-all active:scale-95"
|
||||
>
|
||||
Tout accepter
|
||||
</button>
|
||||
<button
|
||||
onClick={() => setIsVisible(false)}
|
||||
className="absolute top-4 right-4 text-gray-500 hover:text-white transition-colors p-1"
|
||||
aria-label="Fermer"
|
||||
>
|
||||
<X className="w-5 h-5" />
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default CookieBanner;
|
||||
@@ -5,19 +5,31 @@ import React from 'react';
|
||||
import Link from 'next/link';
|
||||
import { usePathname } from 'next/navigation';
|
||||
|
||||
const Footer = () => {
|
||||
interface FooterProps {
|
||||
settings?: any;
|
||||
}
|
||||
|
||||
const Footer = ({ settings }: FooterProps) => {
|
||||
const pathname = usePathname() || '';
|
||||
if (pathname.startsWith('/dashboard')) return null;
|
||||
|
||||
const siteInfo = settings || {
|
||||
siteName: "Afropreunariat",
|
||||
siteSlogan: "La plateforme de référence pour l'entrepreneuriat africain. Visibilité, connexion et croissance pour les TPE/PME.",
|
||||
contactEmail: "support@afropreunariat.com",
|
||||
contactPhone: "+225 00 00 00 00 00",
|
||||
address: "Abidjan, Côte d'Ivoire / Paris, France",
|
||||
footerText: "© 2025 Afropreunariat. Tous droits réservés."
|
||||
};
|
||||
|
||||
return (
|
||||
<footer className="bg-dark-900 text-white">
|
||||
<div className="max-w-7xl mx-auto py-12 px-4 overflow-hidden sm:px-6 lg:px-8">
|
||||
<div className="grid grid-cols-1 md:grid-cols-3 gap-8">
|
||||
<div className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-4 gap-8">
|
||||
<div>
|
||||
<h3 className="text-2xl font-serif font-bold text-brand-500 mb-4">Afropreunariat</h3>
|
||||
<h3 className="text-2xl font-serif font-bold text-brand-500 mb-4">{siteInfo.siteName}</h3>
|
||||
<p className="text-gray-400 text-sm">
|
||||
La plateforme de référence pour l'entrepreneuriat africain.
|
||||
Visibilité, connexion et croissance pour les TPE/PME.
|
||||
{siteInfo.siteSlogan}
|
||||
</p>
|
||||
</div>
|
||||
<div>
|
||||
@@ -30,12 +42,20 @@ const Footer = () => {
|
||||
</div>
|
||||
<div>
|
||||
<h4 className="font-bold text-lg mb-4">Contact</h4>
|
||||
<p className="text-gray-400 text-sm mb-2">Abidjan, Côte d'Ivoire / Paris, France</p>
|
||||
<p className="text-gray-400 text-sm">support@afropreunariat.com</p>
|
||||
<p className="text-gray-400 text-sm mb-2">{siteInfo.address}</p>
|
||||
<p className="text-gray-400 text-sm">{siteInfo.contactEmail}</p>
|
||||
{siteInfo.contactPhone && <p className="text-gray-400 text-sm mt-1">{siteInfo.contactPhone}</p>}
|
||||
</div>
|
||||
<div>
|
||||
<h4 className="font-bold text-lg mb-4">Légal</h4>
|
||||
<ul className="space-y-2 text-gray-400 text-sm">
|
||||
<li><Link href="/cgu" className="hover:text-brand-500 transition-colors">CGU</Link></li>
|
||||
<li><Link href="/cgv" className="hover:text-brand-500 transition-colors">CGV</Link></li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
<div className="mt-8 pt-8 border-t border-gray-800 text-center text-gray-500 text-sm">
|
||||
© 2025 Afropreunariat. Tous droits réservés.
|
||||
{siteInfo.footerText}
|
||||
</div>
|
||||
</div>
|
||||
</footer>
|
||||
|
||||
@@ -13,6 +13,42 @@ services:
|
||||
- "5432:5432"
|
||||
volumes:
|
||||
- pgdata:/var/lib/postgresql/data
|
||||
healthcheck:
|
||||
test: ["CMD-SHELL", "pg_isready -U admin -d afropreunariat_dev"]
|
||||
interval: 10s
|
||||
timeout: 5s
|
||||
retries: 5
|
||||
|
||||
app:
|
||||
build:
|
||||
context: .
|
||||
dockerfile: Dockerfile
|
||||
container_name: afropreunariat_app
|
||||
restart: always
|
||||
ports:
|
||||
- "3000:3000"
|
||||
environment:
|
||||
- DATABASE_URL=postgresql://admin:adminpassword@db:5432/afropreunariat_dev?schema=public
|
||||
- NEXT_PUBLIC_SITE_URL=http://localhost:3000
|
||||
depends_on:
|
||||
db:
|
||||
condition: service_healthy
|
||||
|
||||
admin:
|
||||
build:
|
||||
context: ./admin
|
||||
dockerfile: Dockerfile
|
||||
container_name: afropreunariat_admin
|
||||
restart: always
|
||||
ports:
|
||||
- "3001:3001"
|
||||
environment:
|
||||
- DATABASE_URL=postgresql://admin:adminpassword@db:5432/afropreunariat_dev?schema=public
|
||||
- NEXT_PUBLIC_SITE_URL=http://localhost:3000
|
||||
- NEXT_PUBLIC_ADMIN_URL=http://localhost:3001
|
||||
depends_on:
|
||||
db:
|
||||
condition: service_healthy
|
||||
|
||||
volumes:
|
||||
pgdata:
|
||||
|
||||
40
lib/settings.ts
Normal file
40
lib/settings.ts
Normal file
@@ -0,0 +1,40 @@
|
||||
import { prisma } from './prisma';
|
||||
|
||||
export async function getSiteSettings() {
|
||||
try {
|
||||
const settings = await prisma.siteSetting.findUnique({
|
||||
where: { id: 'singleton' }
|
||||
});
|
||||
|
||||
// Default fallback values if not initialized
|
||||
const defaultSettings = {
|
||||
siteName: "Afropreunariat",
|
||||
siteSlogan: "La plateforme de référence pour l'entrepreneuriat africain. Visibilité, connexion et croissance pour les TPE/PME.",
|
||||
contactEmail: "support@afropreunariat.com",
|
||||
contactPhone: "+225 00 00 00 00 00",
|
||||
address: "Abidjan, Côte d'Ivoire / Paris, France",
|
||||
facebookUrl: "#",
|
||||
twitterUrl: "#",
|
||||
instagramUrl: "#",
|
||||
linkedinUrl: "#",
|
||||
footerText: "© 2025 Afropreunariat. Tous droits réservés."
|
||||
};
|
||||
|
||||
if (!settings) return defaultSettings;
|
||||
return settings;
|
||||
} catch (error) {
|
||||
console.error("Failed to fetch site settings:", error);
|
||||
return {
|
||||
siteName: "Afropreunariat",
|
||||
siteSlogan: "La plateforme de référence pour l'entrepreneuriat africain.",
|
||||
contactEmail: "support@afropreunariat.com",
|
||||
contactPhone: "",
|
||||
address: "",
|
||||
facebookUrl: "#",
|
||||
twitterUrl: "#",
|
||||
instagramUrl: "#",
|
||||
linkedinUrl: "#",
|
||||
footerText: "© 2025 Afropreunariat. Tous droits réservés."
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,7 @@
|
||||
/** @type {import('next').NextConfig} */
|
||||
const nextConfig = {
|
||||
reactStrictMode: true,
|
||||
output: 'standalone',
|
||||
};
|
||||
|
||||
export default nextConfig;
|
||||
|
||||
@@ -193,3 +193,18 @@ enum InterviewType {
|
||||
VIDEO
|
||||
ARTICLE
|
||||
}
|
||||
|
||||
model SiteSetting {
|
||||
id String @id @default("singleton")
|
||||
siteName String @default("Afropreunariat")
|
||||
siteSlogan String @default("La plateforme de référence pour l'entrepreneuriat africain.")
|
||||
contactEmail String @default("support@afropreunariat.com")
|
||||
contactPhone String? @default("+225 00 00 00 00 00")
|
||||
address String? @default("Abidjan, Côte d'Ivoire")
|
||||
facebookUrl String?
|
||||
twitterUrl String?
|
||||
instagramUrl String?
|
||||
linkedinUrl String?
|
||||
footerText String? @default("© 2025 Afropreunariat. Tous droits réservés.")
|
||||
updatedAt DateTime @updatedAt
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user