Compare commits
No commits in common. "18691d930ebdac57b270d7202b5e8c24aee4002e" and "eb5ae1fea791af2cdde675c971b90b48cd97f4f1" have entirely different histories.
18691d930e
...
eb5ae1fea7
11 changed files with 64 additions and 121 deletions
14
.env.example
14
.env.example
|
|
@ -1,16 +1,6 @@
|
|||
# Token StoryBlok — Preview token en dev/preview, Public token en production
|
||||
PUBLIC_STORYBLOK_TOKEN=
|
||||
STORYBLOK_TOKEN=
|
||||
|
||||
# Mettre à true sur l'instance preview (SSR + visual editor StoryBlok)
|
||||
# Ne pas définir ou mettre à false en production (SSG)
|
||||
# PUBLIC_STORYBLOK_IS_PREVIEW=true
|
||||
|
||||
# Webhook rebuild — uniquement sur l'instance preview
|
||||
# Secret partagé avec StoryBlok (Settings > Webhooks > Secret du Webhook)
|
||||
# STORYBLOK_WEBHOOK_SECRET=
|
||||
# Token OAuth Clever Cloud (généré via clever login puis ~/.config/clever-cloud/clever-tools.json)
|
||||
# CLEVER_TOKEN=
|
||||
# ID de l'organisation Clever Cloud (orga_xxxxxxxx)
|
||||
# CLEVER_ORGA_ID=
|
||||
# ID de l'application Clever Cloud de production (app_xxxxxxxx)
|
||||
# CLEVER_APP_ID_PRODUCTION=
|
||||
# STORYBLOK_IS_PREVIEW=true
|
||||
|
|
|
|||
68
README.md
68
README.md
|
|
@ -1,62 +1,20 @@
|
|||
# Compagnie AspiRêves
|
||||
<div align="center">
|
||||
<img width="1200" height="475" alt="GHBanner" src="https://github.com/user-attachments/assets/0aa67016-6eaf-458a-adb2-6e31a0763ed6" />
|
||||
</div>
|
||||
|
||||
Site web de la Compagnie AspiRêves, compagnie de spectacle vivant basée dans le Tarn.
|
||||
# Run and deploy your AI Studio app
|
||||
|
||||
Construit avec [Astro](https://astro.build/) et [StoryBlok](https://www.storyblok.com/) comme CMS headless.
|
||||
This contains everything you need to run your app locally.
|
||||
|
||||
## Prérequis
|
||||
View your app in AI Studio: https://ai.studio/apps/888a76b0-af1d-4274-9218-1817cdc461fb
|
||||
|
||||
- Node.js >= 20
|
||||
- Un fichier `.env` (voir `.env.example`)
|
||||
## Run Locally
|
||||
|
||||
## Développement
|
||||
**Prerequisites:** Node.js
|
||||
|
||||
```bash
|
||||
npm install
|
||||
npm run dev
|
||||
```
|
||||
|
||||
Le serveur démarre sur `http://localhost:3030`.
|
||||
|
||||
## Builds
|
||||
|
||||
Le projet supporte deux modes de build via la variable `PUBLIC_STORYBLOK_IS_PREVIEW` :
|
||||
|
||||
### Production (SSG)
|
||||
|
||||
Site statique, performant, sans serveur Node.js. Utilise le **Public Access Token** StoryBlok et ne récupère que le contenu **publié**.
|
||||
|
||||
```bash
|
||||
npm run build
|
||||
```
|
||||
|
||||
Génère des fichiers HTML statiques dans `dist/`. Le rebuild doit être déclenché par un **webhook StoryBlok** (Settings > Webhooks) à chaque publication de contenu.
|
||||
|
||||
### Preview (SSR)
|
||||
|
||||
Serveur Node.js avec le **visual editor StoryBlok** (bridge + live preview). Utilise le **Preview Access Token** et récupère le contenu en **draft**.
|
||||
|
||||
```bash
|
||||
PUBLIC_STORYBLOK_IS_PREVIEW=true npm run build
|
||||
HOST=0.0.0.0 node dist/server/entry.mjs
|
||||
```
|
||||
|
||||
Le serveur démarre sur le port `8080` par défaut (configurable via `PORT`).
|
||||
|
||||
### Variables d'environnement
|
||||
|
||||
| Variable | Production | Preview |
|
||||
|---|---|---|
|
||||
| `PUBLIC_STORYBLOK_TOKEN` | Public Access Token | Preview Access Token |
|
||||
| `PUBLIC_STORYBLOK_IS_PREVIEW` | *(non défini)* | `true` |
|
||||
| `STORYBLOK_WEBHOOK_SECRET` | - | Secret du webhook StoryBlok |
|
||||
| `CLEVER_TOKEN` | - | Token OAuth Clever Cloud |
|
||||
| `CLEVER_ORGA_ID` | - | ID de l'organisation (orga_xxx) |
|
||||
| `CLEVER_APP_ID_PRODUCTION` | - | ID de l'app production (app_xxx) |
|
||||
| `CC_POST_BUILD_HOOK` | `npm run build` | `npm run build` |
|
||||
| `HOST` | - | `0.0.0.0` |
|
||||
|
||||
### Configuration StoryBlok
|
||||
|
||||
- **Settings > Visual Editor** : mettre l'URL de l'instance preview comme environnement par défaut
|
||||
- **Settings > Webhooks** : configurer un webhook `POST` vers `https://<preview-instance>/api/rebuild` pour déclencher le rebuild production à chaque publication
|
||||
1. Install dependencies:
|
||||
`npm install`
|
||||
2. Set the `GEMINI_API_KEY` in [.env.local](.env.local) to your Gemini API key
|
||||
3. Run the app:
|
||||
`npm run dev`
|
||||
|
|
|
|||
|
|
@ -6,8 +6,8 @@ import icon from 'astro-icon';
|
|||
import node from '@astrojs/node';
|
||||
import { storyblok } from '@storyblok/astro';
|
||||
|
||||
const env = loadEnv('', process.cwd(), 'PUBLIC_STORYBLOK');
|
||||
const isPreview = env.PUBLIC_STORYBLOK_IS_PREVIEW === 'true';
|
||||
const env = loadEnv('', process.cwd(), 'STORYBLOK');
|
||||
const isPreview = env.STORYBLOK_IS_PREVIEW === 'true';
|
||||
|
||||
export default defineConfig({
|
||||
output: isPreview ? 'server' : 'static',
|
||||
|
|
@ -15,7 +15,7 @@ export default defineConfig({
|
|||
integrations: [
|
||||
icon(),
|
||||
storyblok({
|
||||
accessToken: env.PUBLIC_STORYBLOK_TOKEN,
|
||||
accessToken: env.STORYBLOK_TOKEN,
|
||||
bridge: isPreview,
|
||||
livePreview: isPreview,
|
||||
}),
|
||||
|
|
|
|||
|
|
@ -7,7 +7,7 @@
|
|||
"dev": "astro dev --port 3030 --host 0.0.0.0",
|
||||
"build": "astro build",
|
||||
"preview": "astro preview",
|
||||
"start": "HOST=0.0.0.0 node dist/server/entry.mjs",
|
||||
"start": "node dist/server/entry.mjs",
|
||||
"clean": "rm -rf dist"
|
||||
},
|
||||
"dependencies": {
|
||||
|
|
|
|||
|
|
@ -26,5 +26,22 @@ const { title = 'Compagnie AspiRêves' } = Astro.props;
|
|||
<Footer />
|
||||
<ThemeSwitcher />
|
||||
|
||||
<script>
|
||||
const observer = new IntersectionObserver(
|
||||
(entries) => {
|
||||
entries.forEach((entry) => {
|
||||
if (entry.isIntersecting) {
|
||||
entry.target.classList.add('visible');
|
||||
observer.unobserve(entry.target);
|
||||
}
|
||||
});
|
||||
},
|
||||
{ threshold: 0.1, rootMargin: '-50px' }
|
||||
);
|
||||
|
||||
document.querySelectorAll('.fade-up, .fade-right, .fade-scale').forEach((el) => {
|
||||
observer.observe(el);
|
||||
});
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@ import { useStoryblokApi } from '@storyblok/astro';
|
|||
import type { SbBlokData } from '@storyblok/astro';
|
||||
|
||||
function getVersion(): 'draft' | 'published' {
|
||||
return import.meta.env.PUBLIC_STORYBLOK_IS_PREVIEW === 'true' ? 'draft' : 'published';
|
||||
return import.meta.env.STORYBLOK_IS_PREVIEW === 'true' ? 'draft' : 'published';
|
||||
}
|
||||
|
||||
export interface Spectacle {
|
||||
|
|
|
|||
1
src/middleware.ts
Normal file
1
src/middleware.ts
Normal file
|
|
@ -0,0 +1 @@
|
|||
export { onRequest } from '@storyblok/astro/middleware.ts';
|
||||
|
|
@ -17,6 +17,10 @@ const now = new Date();
|
|||
<div class="max-w-5xl mx-auto px-4 sm:px-6 lg:px-8 relative z-10">
|
||||
<!-- Header -->
|
||||
<div class="animate-entrance text-center mb-12 md:mb-20">
|
||||
<div class="inline-flex items-center gap-2 mb-6 px-4 py-1 rounded-full bg-white/50 border border-white/20 text-dream-coral">
|
||||
<Icon name="lucide:calendar" size={16} />
|
||||
<span class="font-sans text-[10px] md:text-xs font-bold uppercase tracking-widest">Calendrier</span>
|
||||
</div>
|
||||
<h1 class="font-display text-4xl sm:text-6xl md:text-8xl text-night mb-8">L'<span class="text-dream-coral italic">Agenda</span></h1>
|
||||
<p class="font-sans text-night/60 max-w-2xl mx-auto text-lg md:text-xl leading-relaxed">
|
||||
Venez nous voir sur scène ! <br class="hidden sm:block" />Chaque date est une nouvelle aventure.
|
||||
|
|
|
|||
|
|
@ -1,36 +0,0 @@
|
|||
import type { APIRoute } from 'astro';
|
||||
import { createHmac, timingSafeEqual } from 'node:crypto';
|
||||
|
||||
export const POST: APIRoute = async ({ request }) => {
|
||||
const webhookSecret = import.meta.env.STORYBLOK_WEBHOOK_SECRET;
|
||||
const token = import.meta.env.CLEVER_TOKEN;
|
||||
const orgaId = import.meta.env.CLEVER_ORGA_ID;
|
||||
const appId = import.meta.env.CLEVER_APP_ID_PRODUCTION;
|
||||
|
||||
if (!webhookSecret || !token || !orgaId || !appId) {
|
||||
return new Response('Missing server configuration', { status: 500 });
|
||||
}
|
||||
|
||||
const body = await request.text();
|
||||
const signature = request.headers.get('webhook-signature') ?? '';
|
||||
const expected = createHmac('sha1', webhookSecret).update(body).digest('hex');
|
||||
|
||||
if (!timingSafeEqual(Buffer.from(signature), Buffer.from(expected))) {
|
||||
return new Response('Invalid signature', { status: 401 });
|
||||
}
|
||||
|
||||
const response = await fetch(
|
||||
`https://api-bridge.clever-cloud.com/v2/organisations/${orgaId}/applications/${appId}/instances`,
|
||||
{
|
||||
method: 'POST',
|
||||
headers: { Authorization: `Bearer ${token}` },
|
||||
},
|
||||
);
|
||||
|
||||
if (!response.ok) {
|
||||
const error = await response.text();
|
||||
return new Response(`Clever Cloud API error: ${response.status} ${error}`, { status: 502 });
|
||||
}
|
||||
|
||||
return new Response('Rebuild triggered', { status: 200 });
|
||||
};
|
||||
|
|
@ -14,6 +14,9 @@ import { companyInfo } from '../data';
|
|||
<!-- Header -->
|
||||
<div class="animate-entrance text-center mb-16 md:mb-24">
|
||||
<h1 class="font-display text-4xl sm:text-6xl md:text-8xl text-night mb-8 leading-tight">La <span class="text-dream-coral italic">Compagnie</span></h1>
|
||||
<p class="font-sans text-night/60 max-w-2xl mx-auto text-lg md:text-xl leading-relaxed">
|
||||
{companyInfo.description}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<!-- Présentation -->
|
||||
|
|
|
|||
|
|
@ -54,33 +54,39 @@ body {
|
|||
animation-delay: 4s;
|
||||
}
|
||||
|
||||
/* Entrance animations */
|
||||
/* Scroll-triggered animations (Intersection Observer) */
|
||||
|
||||
.fade-up {
|
||||
animation: fadeUp 0.6s ease both;
|
||||
opacity: 0;
|
||||
transform: translateY(2rem);
|
||||
transition: opacity 0.6s ease, transform 0.6s ease;
|
||||
}
|
||||
|
||||
@keyframes fadeUp {
|
||||
from { opacity: 0; transform: translateY(2rem); }
|
||||
to { opacity: 1; transform: translateY(0); }
|
||||
.fade-up.visible {
|
||||
opacity: 1;
|
||||
transform: translateY(0);
|
||||
}
|
||||
|
||||
.fade-right {
|
||||
animation: fadeRight 0.6s ease both;
|
||||
opacity: 0;
|
||||
transform: translateX(2rem);
|
||||
transition: opacity 0.6s ease, transform 0.6s ease;
|
||||
}
|
||||
|
||||
@keyframes fadeRight {
|
||||
from { opacity: 0; transform: translateX(2rem); }
|
||||
to { opacity: 1; transform: translateX(0); }
|
||||
.fade-right.visible {
|
||||
opacity: 1;
|
||||
transform: translateX(0);
|
||||
}
|
||||
|
||||
.fade-scale {
|
||||
animation: fadeScale 1s ease both;
|
||||
opacity: 0;
|
||||
transform: scale(0.9);
|
||||
transition: opacity 1s ease, transform 1s ease;
|
||||
}
|
||||
|
||||
@keyframes fadeScale {
|
||||
from { opacity: 0; transform: scale(0.9); }
|
||||
to { opacity: 1; transform: scale(1); }
|
||||
.fade-scale.visible {
|
||||
opacity: 1;
|
||||
transform: scale(1);
|
||||
}
|
||||
|
||||
/* Page entrance animation */
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue