mirror of
https://github.com/suitenumerique/docs.git
synced 2026-05-07 07:32:33 +02:00
Compare commits
1 Commits
documentat
...
v3.1.0-pre
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
baf1cf6862 |
@@ -8,11 +8,6 @@ and this project adheres to
|
||||
|
||||
## [Unreleased]
|
||||
|
||||
## Added
|
||||
|
||||
- 🚩 add homepage feature flag #861
|
||||
|
||||
|
||||
## [3.1.0] - 2025-04-07
|
||||
|
||||
## Added
|
||||
|
||||
@@ -48,10 +48,6 @@ All commit messages must adhere to the following format:
|
||||
Implemented login and signup features, and integrated OAuth2 for social login.
|
||||
```
|
||||
|
||||
## Signing commits
|
||||
|
||||
Only signed commits are accepted. They can be signed using a SSH or GPG key. Github documentation about signing commits contains all the information you need : https://docs.github.com/en/authentication/managing-commit-signature-verification/about-commit-signature-verification#about-commit-signature-verification
|
||||
|
||||
## Changelog Update
|
||||
|
||||
Please add a line to the changelog describing your development. The changelog entry should include a brief summary of the changes, this helps in tracking changes effectively and keeping everyone informed. We usually include the title of the pull request, followed by the pull request ID to finish the log entry. The changelog line should be less than 80 characters in total.
|
||||
|
||||
@@ -48,7 +48,12 @@ Docs is a collaborative text editor designed to address common challenges in kno
|
||||
|
||||
### Test it
|
||||
|
||||
Test Docs on your browser by visiting this [demo document](https://impress-preprod.beta.numerique.gouv.fr/docs/6ee5aac4-4fb9-457d-95bf-bb56c2467713/)
|
||||
Test Docs on your browser by logging in on this [environment](https://impress-preprod.beta.numerique.gouv.fr/)
|
||||
|
||||
```
|
||||
email: test.docs@yopmail.com
|
||||
password: I'd<3ToTestDocs
|
||||
```
|
||||
|
||||
### Run it locally
|
||||
|
||||
|
||||
@@ -1,193 +0,0 @@
|
||||
## Decision TLDR;
|
||||
|
||||
We will use Yjs a CRDT-based library for the collaborative editing of the documents.
|
||||
|
||||
## Status
|
||||
|
||||
Accepted
|
||||
|
||||
## Context
|
||||
|
||||
We need to implement a collaborative editing feature for the documents that supports real-time collaboration, offline capabilities, and seamless integration with our Django backend.
|
||||
|
||||
## Considered alternatives
|
||||
|
||||
### ProseMirror
|
||||
|
||||
A robust toolkit for building rich-text editors with collaboration capabilities.
|
||||
|
||||
| Pros | Cons |
|
||||
| --- | --- |
|
||||
| Mature ecosystem | Complex integration with Django |
|
||||
| Rich text editing features | Steeper learning curve |
|
||||
| Used by major companies | More complex to implement offline support |
|
||||
| Large community | |
|
||||
|
||||
### ShareDB
|
||||
|
||||
Real-time database backend based on Operational Transformation.
|
||||
|
||||
| Pros | Cons |
|
||||
| --- | --- |
|
||||
| Battle-tested in production | Complex setup required |
|
||||
| Strong consistency model | Requires specific backend architecture |
|
||||
| Good documentation | Less flexible with different backends |
|
||||
| | Higher latency compared to CRDTs |
|
||||
|
||||
### Convergence
|
||||
|
||||
Complete enterprise solution for real-time collaboration.
|
||||
|
||||
| Pros | Cons |
|
||||
| --- | --- |
|
||||
| Full-featured solution | Commercial licensing |
|
||||
| Built-in presence features | Less community support |
|
||||
| Enterprise support | More expensive |
|
||||
| Good offline support | Overkill for basic needs |
|
||||
|
||||
### CRDT-based Solutions Comparison
|
||||
|
||||
A CRDT-based library specifically designed for real-time collaboration.
|
||||
|
||||
| Category | Pros | Cons |
|
||||
|----------|------|------|
|
||||
| Technical Implementation | • Native real-time collaboration<br>• No central conflict resolution needed<br>• Works well with Django backend<br>• Automatic state synchronization | • Learning curve for CRDT concepts<br>• More complex initial setup<br>• Additional metadata overhead |
|
||||
| User Experience | • Instant local updates<br>• Works offline by default<br>• Low latency<br>• Smooth concurrent editing | • Eventual consistency might cause brief inconsistencies<br>• UI must handle temporary conflicts |
|
||||
| Performance | • Excellent scaling with multiple users<br>• Reduced server load<br>• Efficient network usage<br>• Good memory optimization (especially Yjs) | • Slightly higher memory usage<br>• Initial state sync can be larger |
|
||||
| Development | • No need to build conflict resolution<br>• Simple integration with text editors<br>• Future-proof architecture | • Team needs to learn new concepts<br>• Fewer ready-made solutions<br>• May need to build some features from scratch |
|
||||
| Maintenance | • Less server infrastructure<br>• Simpler deployment<br>• Fewer points of failure | • Debugging can be more complex<br>• State management requires careful handling |
|
||||
| Business Impact | • Better offline support for users<br>• Scales well as user base grows<br>• No licensing costs (with Yjs) | • Initial development time might be longer<br>• Team training required |
|
||||
|
||||
#### Yjs
|
||||
- **Type**: State-based CRDT
|
||||
- **Implementation**: JavaScript/TypeScript
|
||||
- **Features**:
|
||||
- Rich text collaboration
|
||||
- Shared types (Array, Map, XML)
|
||||
- Binary encoding
|
||||
- P2P support
|
||||
- **Performance**: Excellent for text editing
|
||||
- **Memory Usage**: Optimized
|
||||
- **License**: MIT
|
||||
|
||||
#### Automerge
|
||||
- **Type**: Operation-based CRDT
|
||||
- **Implementation**: JavaScript/Rust
|
||||
- **Features**:
|
||||
- JSON-like data structures
|
||||
- Change history
|
||||
- Undo/Redo
|
||||
- Binary format
|
||||
- **Performance**: Good, with Rust backend
|
||||
- **Memory Usage**: Higher than Yjs
|
||||
- **License**: MIT
|
||||
|
||||
#### Legion
|
||||
- **Type**: State-based CRDT
|
||||
- **Implementation**: Rust with JS bindings
|
||||
- **Features**:
|
||||
- High performance
|
||||
- Memory efficient
|
||||
- Binary protocol
|
||||
- **Performance**: Excellent
|
||||
- **Memory Usage**: Very efficient
|
||||
- **License**: Apache 2.0
|
||||
|
||||
#### Diamond Types
|
||||
- **Type**: Operation-based CRDT
|
||||
- **Implementation**: TypeScript
|
||||
- **Features**:
|
||||
- Specialized for text
|
||||
- Small memory footprint
|
||||
- Simple API
|
||||
- **Performance**: Good for text
|
||||
- **Memory Usage**: Efficient
|
||||
- **License**: MIT
|
||||
|
||||
Comparison Table:
|
||||
|
||||
| Feature | Yjs | Automerge | Legion | Diamond Types |
|
||||
|---------|-----|-----------|--------|---------------|
|
||||
| Text Editing | ✅ Excellent | ✅ Good | ⚠️ Basic | ✅ Excellent |
|
||||
| Structured Data | ✅ | ✅ | ✅ | ⚠️ |
|
||||
| Memory Efficiency | ✅ High | ⚠️ Medium | ✅ Very High | ✅ High |
|
||||
| Network Efficiency | ✅ | ⚠️ | ✅ | ✅ |
|
||||
| Maturity | ✅ | ✅ | ⚠️ | ⚠️ |
|
||||
| Community Size | ✅ Large | ✅ Large | ⚠️ Small | ⚠️ Small |
|
||||
| Documentation | ✅ | ✅ | ⚠️ | ⚠️ |
|
||||
| Backend Options | ✅ Many | ✅ Many | ⚠️ Limited | ⚠️ Limited |
|
||||
|
||||
Key Differences:
|
||||
1. **Implementation Approach**:
|
||||
- Yjs: Optimized for text and rich-text editing
|
||||
- Automerge: General-purpose JSON CRDT
|
||||
- Legion: Performance-focused with Rust
|
||||
- Diamond Types: Specialized for text collaboration
|
||||
|
||||
2. **Performance Characteristics**:
|
||||
- Yjs: Best for text editing scenarios
|
||||
- Automerge: Good all-around performance
|
||||
- Legion: Excellent raw performance
|
||||
- Diamond Types: Optimized for text
|
||||
|
||||
3. **Ecosystem Integration**:
|
||||
- Yjs: Wide range of integrations
|
||||
- Automerge: Good JavaScript ecosystem
|
||||
- Legion: Limited but growing
|
||||
- Diamond Types: Focused on text editors
|
||||
|
||||
This analysis reinforces our choice of Yjs for the CRDT-based option as it provides:
|
||||
- Best-in-class text editing performance
|
||||
- Mature ecosystem
|
||||
- Active community
|
||||
- Excellent documentation
|
||||
- Wide range of backend options
|
||||
|
||||
## Decision
|
||||
|
||||
After evaluating the alternatives, we choose Yjs for the following reasons:
|
||||
|
||||
1. **Technical Fit:**
|
||||
- Native CRDT support ensures reliable collaboration
|
||||
- Excellent offline capabilities
|
||||
- Good performance characteristics
|
||||
- Flexible backend integration options
|
||||
|
||||
2. **Project Requirements Match:**
|
||||
- Easy integration with our Django backend
|
||||
- Supports our core collaborative features
|
||||
- Manageable learning curve for the team
|
||||
|
||||
3. **Community & Support:**
|
||||
- Active development
|
||||
- Growing community
|
||||
- Good documentation
|
||||
- Open source with MIT license
|
||||
|
||||
### Comparison of Key Features:
|
||||
|
||||
| Feature | Yjs (CRDT) | ProseMirror | ShareDB | Convergence |
|
||||
|---------|-----|-------------|----------|-------------|
|
||||
| Real-time Collaboration | ✅ | ✅ | ✅ | ✅ |
|
||||
| Offline Support | ✅ | ⚠️ | ⚠️ | ✅ |
|
||||
| Django Integration | Easy | Complex | Complex | Moderate |
|
||||
| Learning Curve | Medium | High | High | Medium |
|
||||
| Cost | Free | Free | Free | Paid |
|
||||
| Community Size | Growing | Large | Medium | Small |
|
||||
|
||||
## Consequences
|
||||
|
||||
### Positive
|
||||
- Simplified implementation of real-time collaboration
|
||||
- Good developer experience
|
||||
- Future-proof technology choice
|
||||
- No licensing costs
|
||||
|
||||
### Negative
|
||||
- Team needs to learn CRDT concepts
|
||||
- Newer technology compared to alternatives
|
||||
- May need to build some features available out-of-the-box in other solutions
|
||||
|
||||
### Risks
|
||||
- Community support might not grow as expected
|
||||
- May discover limitations as we scale
|
||||
@@ -1,19 +0,0 @@
|
||||
## Architecture
|
||||
|
||||
### Global system architecture
|
||||
|
||||
```mermaid
|
||||
flowchart TD
|
||||
User -- HTTP --> Front("Frontend (NextJS SPA)")
|
||||
Front -- REST API --> Back("Backend (Django)")
|
||||
Front -- WebSocket --> Yserver("Microservice Yjs (Express)") -- WebSocket --> CollaborationServer("Collaboration server (Hocuspocus)") -- REST API <--> Back
|
||||
Front -- OIDC --> Back -- OIDC ---> OIDC("Keycloak / ProConnect")
|
||||
Back -- REST API --> Yserver
|
||||
Back --> DB("Database (PostgreSQL)")
|
||||
Back <--> Celery --> DB
|
||||
Back ----> S3("Minio (S3)")
|
||||
```
|
||||
|
||||
### Architecture decision records
|
||||
|
||||
- [ADR-0001-20250106-use-yjs-for-docs-editing](./adr/ADR-0001-20250106-use-yjs-for-docs-editing.md)
|
||||
@@ -47,11 +47,6 @@ These are the environmental variables you can set for the impress-backend contai
|
||||
| COLLABORATION_API_URL | collaboration api host | |
|
||||
| COLLABORATION_SERVER_SECRET | collaboration api secret | |
|
||||
| COLLABORATION_WS_URL | collaboration websocket url | |
|
||||
| FRONTEND_CSS_URL | To add a external css file to the app | |
|
||||
| FRONTEND_HOMEPAGE_FEATURE_ENABLED | frontend feature flag to display the homepage | false |
|
||||
| FRONTEND_FOOTER_FEATURE_ENABLED | frontend feature flag to display the footer | false |
|
||||
| FRONTEND_FOOTER_VIEW_CACHE_TIMEOUT | Cache duration of the json footer | 86400 |
|
||||
| FRONTEND_URL_JSON_FOOTER | Url with a json to configure the footer | |
|
||||
| FRONTEND_THEME | frontend theme to use | |
|
||||
| POSTHOG_KEY | posthog key for analytics | |
|
||||
| CRISP_WEBSITE_ID | crisp website id for support | |
|
||||
|
||||
@@ -64,6 +64,5 @@ COLLABORATION_WS_URL=ws://localhost:4444/collaboration/ws/
|
||||
|
||||
# Frontend
|
||||
FRONTEND_THEME=default
|
||||
FRONTEND_HOMEPAGE_FEATURE_ENABLED=True
|
||||
FRONTEND_FOOTER_FEATURE_ENABLED=True
|
||||
FRONTEND_URL_JSON_FOOTER=http://frontend:3000/contents/footer-demo.json
|
||||
|
||||
@@ -1692,7 +1692,6 @@ class ConfigView(drf.views.APIView):
|
||||
"CRISP_WEBSITE_ID",
|
||||
"ENVIRONMENT",
|
||||
"FRONTEND_CSS_URL",
|
||||
"FRONTEND_HOMEPAGE_FEATURE_ENABLED",
|
||||
"FRONTEND_FOOTER_FEATURE_ENABLED",
|
||||
"FRONTEND_THEME",
|
||||
"MEDIA_BASE_URL",
|
||||
|
||||
@@ -19,7 +19,6 @@ pytestmark = pytest.mark.django_db
|
||||
COLLABORATION_WS_URL="http://testcollab/",
|
||||
CRISP_WEBSITE_ID="123",
|
||||
FRONTEND_CSS_URL="http://testcss/",
|
||||
FRONTEND_HOMEPAGE_FEATURE_ENABLED=True,
|
||||
FRONTEND_FOOTER_FEATURE_ENABLED=True,
|
||||
FRONTEND_THEME="test-theme",
|
||||
MEDIA_BASE_URL="http://testserver/",
|
||||
@@ -42,7 +41,6 @@ def test_api_config(is_authenticated):
|
||||
"CRISP_WEBSITE_ID": "123",
|
||||
"ENVIRONMENT": "test",
|
||||
"FRONTEND_CSS_URL": "http://testcss/",
|
||||
"FRONTEND_HOMEPAGE_FEATURE_ENABLED": True,
|
||||
"FRONTEND_FOOTER_FEATURE_ENABLED": True,
|
||||
"FRONTEND_THEME": "test-theme",
|
||||
"LANGUAGES": [
|
||||
|
||||
@@ -410,11 +410,6 @@ class Base(Configuration):
|
||||
FRONTEND_THEME = values.Value(
|
||||
None, environ_name="FRONTEND_THEME", environ_prefix=None
|
||||
)
|
||||
FRONTEND_HOMEPAGE_FEATURE_ENABLED = values.BooleanValue(
|
||||
default=False,
|
||||
environ_name="FRONTEND_HOMEPAGE_FEATURE_ENABLED",
|
||||
environ_prefix=None,
|
||||
)
|
||||
FRONTEND_URL_JSON_FOOTER = values.Value(
|
||||
None, environ_name="FRONTEND_URL_JSON_FOOTER", environ_prefix=None
|
||||
)
|
||||
|
||||
@@ -1,26 +1,5 @@
|
||||
import { Page, expect } from '@playwright/test';
|
||||
|
||||
export const CONFIG = {
|
||||
AI_FEATURE_ENABLED: true,
|
||||
CRISP_WEBSITE_ID: null,
|
||||
COLLABORATION_WS_URL: 'ws://localhost:4444/collaboration/ws/',
|
||||
ENVIRONMENT: 'development',
|
||||
FRONTEND_CSS_URL: null,
|
||||
FRONTEND_HOMEPAGE_FEATURE_ENABLED: true,
|
||||
FRONTEND_FOOTER_FEATURE_ENABLED: true,
|
||||
FRONTEND_THEME: 'default',
|
||||
MEDIA_BASE_URL: 'http://localhost:8083',
|
||||
LANGUAGES: [
|
||||
['en-us', 'English'],
|
||||
['fr-fr', 'Français'],
|
||||
['de-de', 'Deutsch'],
|
||||
['nl-nl', 'Nederlands'],
|
||||
],
|
||||
LANGUAGE_CODE: 'en-us',
|
||||
POSTHOG_KEY: {},
|
||||
SENTRY_DSN: null,
|
||||
};
|
||||
|
||||
export const keyCloakSignIn = async (
|
||||
page: Page,
|
||||
browserName: string,
|
||||
|
||||
@@ -2,7 +2,27 @@ import path from 'path';
|
||||
|
||||
import { expect, test } from '@playwright/test';
|
||||
|
||||
import { CONFIG, createDoc } from './common';
|
||||
import { createDoc } from './common';
|
||||
|
||||
const config = {
|
||||
AI_FEATURE_ENABLED: true,
|
||||
CRISP_WEBSITE_ID: null,
|
||||
COLLABORATION_WS_URL: 'ws://localhost:4444/collaboration/ws/',
|
||||
ENVIRONMENT: 'development',
|
||||
FRONTEND_CSS_URL: null,
|
||||
FRONTEND_FOOTER_FEATURE_ENABLED: true,
|
||||
FRONTEND_THEME: 'default',
|
||||
MEDIA_BASE_URL: 'http://localhost:8083',
|
||||
LANGUAGES: [
|
||||
['en-us', 'English'],
|
||||
['fr-fr', 'Français'],
|
||||
['de-de', 'Deutsch'],
|
||||
['nl-nl', 'Nederlands'],
|
||||
],
|
||||
LANGUAGE_CODE: 'en-us',
|
||||
POSTHOG_KEY: {},
|
||||
SENTRY_DSN: null,
|
||||
};
|
||||
|
||||
test.describe('Config', () => {
|
||||
test('it checks the config api is called', async ({ page }) => {
|
||||
@@ -16,7 +36,7 @@ test.describe('Config', () => {
|
||||
const response = await responsePromise;
|
||||
expect(response.ok()).toBeTruthy();
|
||||
|
||||
expect(await response.json()).toStrictEqual(CONFIG);
|
||||
expect(await response.json()).toStrictEqual(config);
|
||||
});
|
||||
|
||||
test('it checks that sentry is trying to init from config endpoint', async ({
|
||||
@@ -27,7 +47,7 @@ test.describe('Config', () => {
|
||||
if (request.method().includes('GET')) {
|
||||
await route.fulfill({
|
||||
json: {
|
||||
...CONFIG,
|
||||
...config,
|
||||
SENTRY_DSN: 'https://sentry.io/123',
|
||||
},
|
||||
});
|
||||
@@ -100,7 +120,7 @@ test.describe('Config', () => {
|
||||
if (request.method().includes('GET')) {
|
||||
await route.fulfill({
|
||||
json: {
|
||||
...CONFIG,
|
||||
...config,
|
||||
AI_FEATURE_ENABLED: false,
|
||||
},
|
||||
});
|
||||
@@ -131,7 +151,7 @@ test.describe('Config', () => {
|
||||
if (request.method().includes('GET')) {
|
||||
await route.fulfill({
|
||||
json: {
|
||||
...CONFIG,
|
||||
...config,
|
||||
CRISP_WEBSITE_ID: '1234',
|
||||
},
|
||||
});
|
||||
@@ -153,7 +173,7 @@ test.describe('Config', () => {
|
||||
if (request.method().includes('GET')) {
|
||||
await route.fulfill({
|
||||
json: {
|
||||
...CONFIG,
|
||||
...config,
|
||||
FRONTEND_CSS_URL: 'http://localhost:123465/css/style.css',
|
||||
},
|
||||
});
|
||||
|
||||
@@ -1,7 +1,5 @@
|
||||
import { expect, test } from '@playwright/test';
|
||||
|
||||
import { CONFIG } from './common';
|
||||
|
||||
test.beforeEach(async ({ page }) => {
|
||||
await page.goto('/docs/');
|
||||
});
|
||||
@@ -52,27 +50,4 @@ test.describe('Home page', () => {
|
||||
|
||||
await expect(footer).toBeVisible();
|
||||
});
|
||||
|
||||
test('it checks the homepage feature flag', async ({ page }) => {
|
||||
await page.route('**/api/v1.0/config/', async (route) => {
|
||||
const request = route.request();
|
||||
if (request.method().includes('GET')) {
|
||||
await route.fulfill({
|
||||
json: {
|
||||
...CONFIG,
|
||||
FRONTEND_HOMEPAGE_FEATURE_ENABLED: false,
|
||||
},
|
||||
});
|
||||
} else {
|
||||
await route.continue();
|
||||
}
|
||||
});
|
||||
|
||||
await page.goto('/');
|
||||
|
||||
// Keyclock login page
|
||||
await expect(
|
||||
page.locator('.login-pf-page-header').getByText('impress'),
|
||||
).toBeVisible();
|
||||
});
|
||||
});
|
||||
|
||||
@@ -5,18 +5,17 @@ import { Theme } from '@/cunningham/';
|
||||
import { PostHogConf } from '@/services';
|
||||
|
||||
interface ConfigResponse {
|
||||
AI_FEATURE_ENABLED?: boolean;
|
||||
COLLABORATION_WS_URL?: string;
|
||||
CRISP_WEBSITE_ID?: string;
|
||||
ENVIRONMENT: string;
|
||||
FRONTEND_CSS_URL?: string;
|
||||
FRONTEND_HOMEPAGE_FEATURE_ENABLED?: boolean;
|
||||
FRONTEND_THEME?: Theme;
|
||||
LANGUAGES: [string, string][];
|
||||
LANGUAGE_CODE: string;
|
||||
ENVIRONMENT: string;
|
||||
COLLABORATION_WS_URL?: string;
|
||||
CRISP_WEBSITE_ID?: string;
|
||||
FRONTEND_THEME?: Theme;
|
||||
FRONTEND_CSS_URL?: string;
|
||||
MEDIA_BASE_URL?: string;
|
||||
POSTHOG_KEY?: PostHogConf;
|
||||
SENTRY_DSN?: string;
|
||||
AI_FEATURE_ENABLED?: boolean;
|
||||
}
|
||||
|
||||
export const getConfig = async (): Promise<ConfigResponse> => {
|
||||
|
||||
@@ -3,16 +3,14 @@ import { useRouter } from 'next/router';
|
||||
import { PropsWithChildren } from 'react';
|
||||
|
||||
import { Box } from '@/components';
|
||||
import { useConfig } from '@/core';
|
||||
|
||||
import { useAuth } from '../hooks';
|
||||
import { getAuthUrl, gotoLogin } from '../utils';
|
||||
import { getAuthUrl } from '../utils';
|
||||
|
||||
export const Auth = ({ children }: PropsWithChildren) => {
|
||||
const { isLoading, pathAllowed, isFetchedAfterMount, authenticated } =
|
||||
useAuth();
|
||||
const { replace, pathname } = useRouter();
|
||||
const { data: config } = useConfig();
|
||||
|
||||
if (isLoading && !isFetchedAfterMount) {
|
||||
return (
|
||||
@@ -42,11 +40,7 @@ export const Auth = ({ children }: PropsWithChildren) => {
|
||||
* If the user is not authenticated and the path is not allowed, we redirect to the login page.
|
||||
*/
|
||||
if (!authenticated && !pathAllowed) {
|
||||
if (config?.FRONTEND_HOMEPAGE_FEATURE_ENABLED) {
|
||||
void replace('/home');
|
||||
} else {
|
||||
gotoLogin();
|
||||
}
|
||||
void replace('/login');
|
||||
return (
|
||||
<Box $height="100vh" $width="100vw" $align="center" $justify="center">
|
||||
<Loader />
|
||||
@@ -55,9 +49,9 @@ export const Auth = ({ children }: PropsWithChildren) => {
|
||||
}
|
||||
|
||||
/**
|
||||
* If the user is authenticated and the path is the home page, we redirect to the index.
|
||||
* If the user is authenticated and the path is the login page, we redirect to the home page.
|
||||
*/
|
||||
if (pathname === '/home' && authenticated) {
|
||||
if (pathname === '/login' && authenticated) {
|
||||
void replace('/');
|
||||
return (
|
||||
<Box $height="100vh" $width="100vw" $align="center" $justify="center">
|
||||
|
||||
@@ -1,8 +0,0 @@
|
||||
import { HomeContent } from '@/features/home';
|
||||
import { NextPageWithLayout } from '@/types/next';
|
||||
|
||||
const Page: NextPageWithLayout = () => {
|
||||
return <HomeContent />;
|
||||
};
|
||||
|
||||
export default Page;
|
||||
@@ -50,7 +50,6 @@ backend:
|
||||
DB_USER: dinum
|
||||
DB_PASSWORD: pass
|
||||
DB_PORT: 5432
|
||||
FRONTEND_HOMEPAGE_FEATURE_ENABLED: true
|
||||
FRONTEND_FOOTER_FEATURE_ENABLED: true
|
||||
FRONTEND_URL_JSON_FOOTER: https://impress.127.0.0.1.nip.io/contents/footer-demo.json
|
||||
POSTGRES_DB: impress
|
||||
|
||||
Reference in New Issue
Block a user