V1 without presentation styling
This commit is contained in:
1 parent
39705a7b43
commit
3869d67172
295 files changed
+378
-1255
No files matched your search
@@ -1,16 +0,0 @@
|
|||||||
import { getRandomWordList } from "@/words/word-list/utils";
|
|
||||||
|
|
||||||
const getServerSideProps = async () => {
|
|
||||||
const wordList = await getRandomWordList();
|
|
||||||
console.log("Data is being fetch...", wordList)
|
|
||||||
return { props: { wordList:wordList } }
|
|
||||||
}
|
|
||||||
|
|
||||||
export default async function WordList(){
|
|
||||||
const wordList = await getServerSideProps();
|
|
||||||
return (
|
|
||||||
<div data-testid="word-list-view">
|
|
||||||
WordList server props: {JSON.stringify(wordList)}
|
|
||||||
</div>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
@@ -1 +0,0 @@
|
|||||||
export * from './word-list.view'
|
|
||||||
@@ -1,15 +0,0 @@
|
|||||||
import { WordListRepsonse } from './word-list.types'
|
|
||||||
|
|
||||||
const dataURL = 'http://localhost:3000/words/es?complexity=easy&howMany=10'
|
|
||||||
|
|
||||||
export async function getStaticProps() {
|
|
||||||
const randomWordList = await getRandomWordList();
|
|
||||||
return {
|
|
||||||
props: { randomWordList }, // will be passed to the page component as props
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export async function getRandomWordList() {
|
|
||||||
const wordList: WordListRepsonse = await fetch(dataURL).then((response) => response.json());
|
|
||||||
return wordList;
|
|
||||||
}
|
|
||||||
@@ -1,3 +0,0 @@
|
|||||||
.container {
|
|
||||||
|
|
||||||
}
|
|
||||||
@@ -1,23 +0,0 @@
|
|||||||
import type { Meta, StoryObj } from '@storybook/react'
|
|
||||||
import { expect } from '@storybook/jest'
|
|
||||||
import { within } from '@storybook/testing-library'
|
|
||||||
import { WordListView } from './word-list.view'
|
|
||||||
|
|
||||||
const meta: Meta<typeof WordListView> = {
|
|
||||||
title: 'WordListView',
|
|
||||||
component: WordListView,
|
|
||||||
argTypes: {},
|
|
||||||
}
|
|
||||||
|
|
||||||
export default meta
|
|
||||||
type Story = StoryObj<typeof WordListView>
|
|
||||||
|
|
||||||
export const Default: Story = {
|
|
||||||
args: {},
|
|
||||||
async play({ canvasElement }) {
|
|
||||||
const canvas = within(canvasElement)
|
|
||||||
const container = canvas.getByTestId('word-list-view')
|
|
||||||
|
|
||||||
expect(container).toBeTruthy()
|
|
||||||
},
|
|
||||||
}
|
|
||||||
@@ -1,8 +0,0 @@
|
|||||||
export type WordElement = {
|
|
||||||
word: string
|
|
||||||
correct?: boolean
|
|
||||||
}
|
|
||||||
|
|
||||||
export type WordListRepsonse = {
|
|
||||||
wordList: WordElement[]
|
|
||||||
}
|
|
||||||
@@ -1,15 +0,0 @@
|
|||||||
import React from 'react'
|
|
||||||
import styles from './word-list.module.css'
|
|
||||||
import { InferGetServerSidePropsType, InferGetStaticPropsType } from 'next'
|
|
||||||
import { getRandomWordList, getStaticProps } from './utils'
|
|
||||||
|
|
||||||
export function WordListView(
|
|
||||||
props: InferGetStaticPropsType<typeof getStaticProps>
|
|
||||||
) {
|
|
||||||
return (
|
|
||||||
<div data-testid="word-list-view" className={styles.container}>
|
|
||||||
WordList: {JSON.stringify(props?.randomWordList)}
|
|
||||||
</div>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
@@ -1,5 +0,0 @@
|
|||||||
@tailwind base;
|
|
||||||
@tailwind components;
|
|
||||||
@tailwind utilities;
|
|
||||||
|
|
||||||
@import url('https://fonts.googleapis.com/css2?family=Lexend:wght@100;200;300;400;500;600;700;800;900&display=swap');
|
|
||||||
Submodule examples/front-arch-ssr-react deleted from febdeb7828.
@@ -1,15 +0,0 @@
|
|||||||
# Generated files
|
|
||||||
node_modules
|
|
||||||
output
|
|
||||||
dist
|
|
||||||
|
|
||||||
# Editor directories and files
|
|
||||||
.vscode/*
|
|
||||||
!.vscode/extensions.json
|
|
||||||
.idea
|
|
||||||
.DS_Store
|
|
||||||
*.suo
|
|
||||||
*.ntvs*
|
|
||||||
*.njsproj
|
|
||||||
*.sln
|
|
||||||
*.sw?
|
|
||||||
File diff suppressed because it is too large.
Load diff
@@ -1,20 +0,0 @@
|
|||||||
{
|
|
||||||
"name": "presentation-animations",
|
|
||||||
"private": true,
|
|
||||||
"version": "0.0.0",
|
|
||||||
"scripts": {
|
|
||||||
"serve": "vite",
|
|
||||||
"build": "tsc && vite build"
|
|
||||||
},
|
|
||||||
"dependencies": {
|
|
||||||
"@motion-canvas/core": "^3.11.0",
|
|
||||||
"@motion-canvas/2d": "^3.11.0",
|
|
||||||
"@motion-canvas/ffmpeg": "^1.1.0"
|
|
||||||
},
|
|
||||||
"devDependencies": {
|
|
||||||
"@motion-canvas/ui": "^3.11.0",
|
|
||||||
"@motion-canvas/vite-plugin": "^3.11.0",
|
|
||||||
"typescript": "^4.9.5",
|
|
||||||
"vite": "^4.1.4"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1 +0,0 @@
|
|||||||
/// <reference types="@motion-canvas/core/project" />
|
|
||||||
@@ -1,32 +0,0 @@
|
|||||||
{
|
|
||||||
"version": 0,
|
|
||||||
"shared": {
|
|
||||||
"background": null,
|
|
||||||
"range": [
|
|
||||||
0,
|
|
||||||
null
|
|
||||||
],
|
|
||||||
"size": {
|
|
||||||
"x": 1920,
|
|
||||||
"y": 1080
|
|
||||||
},
|
|
||||||
"audioOffset": 0
|
|
||||||
},
|
|
||||||
"preview": {
|
|
||||||
"fps": 30,
|
|
||||||
"resolutionScale": 1
|
|
||||||
},
|
|
||||||
"rendering": {
|
|
||||||
"fps": 60,
|
|
||||||
"resolutionScale": 1,
|
|
||||||
"colorSpace": "srgb",
|
|
||||||
"exporter": {
|
|
||||||
"name": "@motion-canvas/core/image-sequence",
|
|
||||||
"options": {
|
|
||||||
"fileType": "image/png",
|
|
||||||
"quality": 100,
|
|
||||||
"groupByScene": false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,7 +0,0 @@
|
|||||||
import {makeProject} from '@motion-canvas/core';
|
|
||||||
|
|
||||||
import example from './scenes/example?scene';
|
|
||||||
|
|
||||||
export default makeProject({
|
|
||||||
scenes: [example],
|
|
||||||
});
|
|
||||||
@@ -1,5 +0,0 @@
|
|||||||
{
|
|
||||||
"version": 0,
|
|
||||||
"timeEvents": [],
|
|
||||||
"seed": 1375832605
|
|
||||||
}
|
|
||||||
@@ -1,22 +0,0 @@
|
|||||||
import {makeScene2D, Circle} from '@motion-canvas/2d';
|
|
||||||
import {all, createRef} from '@motion-canvas/core';
|
|
||||||
|
|
||||||
export default makeScene2D(function* (view) {
|
|
||||||
const myCircle = createRef<Circle>();
|
|
||||||
|
|
||||||
view.add(
|
|
||||||
<Circle
|
|
||||||
ref={myCircle}
|
|
||||||
// try changing these properties:
|
|
||||||
x={-300}
|
|
||||||
width={140}
|
|
||||||
height={140}
|
|
||||||
fill="#e13238"
|
|
||||||
/>,
|
|
||||||
);
|
|
||||||
|
|
||||||
yield* all(
|
|
||||||
myCircle().position.x(300, 1).to(-300, 1),
|
|
||||||
myCircle().fill('#e6a700', 1).to('#e13238', 1),
|
|
||||||
);
|
|
||||||
});
|
|
||||||
@@ -1,4 +0,0 @@
|
|||||||
{
|
|
||||||
"extends": "@motion-canvas/2d/tsconfig.project.json",
|
|
||||||
"include": ["src"]
|
|
||||||
}
|
|
||||||
@@ -1,10 +0,0 @@
|
|||||||
import {defineConfig} from 'vite';
|
|
||||||
import motionCanvas from '@motion-canvas/vite-plugin';
|
|
||||||
import ffmpeg from '@motion-canvas/ffmpeg';
|
|
||||||
|
|
||||||
export default defineConfig({
|
|
||||||
plugins: [
|
|
||||||
motionCanvas(),
|
|
||||||
ffmpeg(),
|
|
||||||
],
|
|
||||||
});
|
|
||||||
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
@@ -1,6 +1,9 @@
|
|||||||
/** @type {import('next').NextConfig} */
|
/** @type {import('next').NextConfig} */
|
||||||
const nextConfig = {
|
const nextConfig = {
|
||||||
reactStrictMode: true,
|
reactStrictMode: true,
|
||||||
|
experimental: {
|
||||||
|
serverActions: true,
|
||||||
|
},
|
||||||
// Avoiding CORS issues
|
// Avoiding CORS issues
|
||||||
// async rewrites() {
|
// async rewrites() {
|
||||||
// return [
|
// return [
|
||||||
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
Binary file not shown.
|
After Width: | Height: | Size: 27 KiB |
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
@@ -0,0 +1,140 @@
|
|||||||
|
'use client'
|
||||||
|
|
||||||
|
import { useRouter } from 'next/navigation'
|
||||||
|
import { useState } from 'react'
|
||||||
|
|
||||||
|
export default function Presentation() {
|
||||||
|
const [current, setCurrent] = useState<number>(0)
|
||||||
|
|
||||||
|
const stages = [
|
||||||
|
<Introduction />,
|
||||||
|
<Benefits />,
|
||||||
|
<Rendering />,
|
||||||
|
<Strategies />,
|
||||||
|
<DataFetching />,
|
||||||
|
]
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="main-presentation">
|
||||||
|
{stages[current]}
|
||||||
|
<button
|
||||||
|
onClick={() =>
|
||||||
|
setCurrent(current + 1 < stages.length ? current + 1 : 0)
|
||||||
|
}
|
||||||
|
>
|
||||||
|
Siguiente
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 1. Introduction
|
||||||
|
// 2. Rendering strategies.
|
||||||
|
// 3. Benefits.
|
||||||
|
// 4. Actual process and why its important to know it.
|
||||||
|
// 5. Serverside data fetching
|
||||||
|
|
||||||
|
function Introduction() {
|
||||||
|
return (
|
||||||
|
<div className="main-presentation-comparation">
|
||||||
|
<h3>Next</h3>
|
||||||
|
<div>
|
||||||
|
<p>Nos da la posibilidad de renderizar desde el servidor</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
function Rendering() {
|
||||||
|
return (
|
||||||
|
<div className="main-presentation-comparation">
|
||||||
|
<h3>Rendering</h3>
|
||||||
|
<div>
|
||||||
|
The rendering works is split into chunks: - By individual route
|
||||||
|
segments - By React [Suspense
|
||||||
|
boundaries](https://react.dev/reference/react/Suspense) (react
|
||||||
|
way of having a fallback while a components has finished
|
||||||
|
loading) Each chunk is rendered in the server, then, on the
|
||||||
|
client: 1. The HTML is used to immediately show fast preview. 2.
|
||||||
|
The server components rendered are inserted to update the DOM
|
||||||
|
(components rendered in server with placeholders for client
|
||||||
|
components and props). 3. `JS` instructions are used to
|
||||||
|
[hydrate?](https://react.dev/reference/react-dom/client/hydrateRoot)
|
||||||
|
Client Components and make the application interactive.
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
function Strategies() {
|
||||||
|
return (
|
||||||
|
<div className="main-presentation-strategies">
|
||||||
|
<h3>Strategies</h3>
|
||||||
|
<div>
|
||||||
|
<ul>
|
||||||
|
<li>
|
||||||
|
- Static rendering (default): Good for static pages:
|
||||||
|
Rendered in build time or in the background after data
|
||||||
|
revalidation
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
- Security: sensitive data is kept int the server (API
|
||||||
|
keys and tokens)
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
- Dynamic rendering: rendered per user request, Next
|
||||||
|
uses this type of rendering automatically when discovers
|
||||||
|
a dynamic function (`cookies()`, `headers()`,
|
||||||
|
`userSearchParams()`).
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
- Streaming: work is split into chunks and streamed as
|
||||||
|
they become ready so the load is progressive.
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
function Benefits() {
|
||||||
|
return (
|
||||||
|
<div className="main-presentation-benefits">
|
||||||
|
<h3>Beneficios</h3>
|
||||||
|
<div>
|
||||||
|
<ul>
|
||||||
|
<li>
|
||||||
|
- Fetch data directly on the server, performance and
|
||||||
|
load benefits.
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
- Security: sensitive data is kept int the server (API
|
||||||
|
keys and tokens)
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
- Caching: results can be cached to improve performance
|
||||||
|
between users.
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
- Bundle size: will be reduced as part of the
|
||||||
|
application will reside in the server.
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
- SEO: because the pages will be rendered the search
|
||||||
|
engine bots will make good use of it.
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
- Streaming: to split the rendering into chunks and
|
||||||
|
stream them as they become ready.
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
function DataFetching() {
|
||||||
|
const router = useRouter()
|
||||||
|
router.push('/word-list')
|
||||||
|
return <>Redirecting...</>
|
||||||
|
}
|
||||||
File renamed without changes.
@@ -0,0 +1,6 @@
|
|||||||
|
'use server'
|
||||||
|
import { revalidateTag } from 'next/cache'
|
||||||
|
|
||||||
|
export async function revalidateFetchByTag(tag: string) {
|
||||||
|
revalidateTag(tag)
|
||||||
|
}
|
||||||
@@ -0,0 +1,31 @@
|
|||||||
|
'use client'
|
||||||
|
|
||||||
|
import { useEffect, useState } from 'react'
|
||||||
|
import { WordListRepsonse, fetchWordlist } from '../utils'
|
||||||
|
|
||||||
|
export default function ClientWordList() {
|
||||||
|
const [data, setData] = useState<WordListRepsonse>()
|
||||||
|
|
||||||
|
const fetchData = () =>
|
||||||
|
fetchWordlist().then((response) => setData(response))
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
fetchData()
|
||||||
|
}, [])
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="data-fetching-client">
|
||||||
|
<h3>
|
||||||
|
Wordlist fetched in the <span>client</span>
|
||||||
|
</h3>
|
||||||
|
<div>
|
||||||
|
{data?.wordList.map((word) => (
|
||||||
|
<span>{word.word}</span>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<button onClick={fetchData}>Revalidate!</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
@@ -0,0 +1,15 @@
|
|||||||
|
'use client'
|
||||||
|
|
||||||
|
import { useRouter } from 'next/navigation'
|
||||||
|
|
||||||
|
export default function GoBackButton() {
|
||||||
|
const router = useRouter()
|
||||||
|
return (
|
||||||
|
<button
|
||||||
|
style={{ position: 'absolute' }}
|
||||||
|
onClick={() => router.push('/presentation')}
|
||||||
|
>
|
||||||
|
Go back to presentation
|
||||||
|
</button>
|
||||||
|
)
|
||||||
|
}
|
||||||
@@ -0,0 +1,11 @@
|
|||||||
|
'use client'
|
||||||
|
|
||||||
|
import { revalidateFetchByTag } from '../actions'
|
||||||
|
|
||||||
|
export default function RevalidateButton() {
|
||||||
|
return (
|
||||||
|
<button onClick={() => revalidateFetchByTag('wordlist')}>
|
||||||
|
Revalidate!
|
||||||
|
</button>
|
||||||
|
)
|
||||||
|
}
|
||||||
@@ -0,0 +1,22 @@
|
|||||||
|
import RevalidateButton from './revalidate-button'
|
||||||
|
import { fetchWordlist } from '../utils'
|
||||||
|
|
||||||
|
export default async function ServerWordList() {
|
||||||
|
const response = await fetchWordlist()
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div data-testid="word-list-view" className="data-fetching-server">
|
||||||
|
<h3>
|
||||||
|
Wordlist fetched in the <span>server</span>
|
||||||
|
</h3>
|
||||||
|
<div>
|
||||||
|
{response?.wordList.map((word) => (
|
||||||
|
<span>{word.word}</span>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<RevalidateButton />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
@@ -0,0 +1,12 @@
|
|||||||
|
import ClientWordList from './components/clientside-word-list'
|
||||||
|
import GoBackButton from './components/go-back-button'
|
||||||
|
import ServerWordList from './components/serverside-word-list'
|
||||||
|
|
||||||
|
export default function WordList() {
|
||||||
|
return (
|
||||||
|
<div className="data-fetching">
|
||||||
|
<ServerWordList />
|
||||||
|
<ClientWordList />
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
@@ -0,0 +1,22 @@
|
|||||||
|
const WORDLIST_API_URL =
|
||||||
|
'http://localhost:3000/words/es?complexity=medium&howMany=30'
|
||||||
|
|
||||||
|
export type WordElement = {
|
||||||
|
word: string
|
||||||
|
correct?: boolean
|
||||||
|
}
|
||||||
|
|
||||||
|
export type WordListRepsonse = {
|
||||||
|
wordList: WordElement[]
|
||||||
|
}
|
||||||
|
|
||||||
|
// fetch("https://...", { cache: "no-store" });
|
||||||
|
export const fetchWordlist = async () => {
|
||||||
|
const wordList: WordListRepsonse = await fetch(WORDLIST_API_URL, {
|
||||||
|
next: { tags: ['wordlist'] },
|
||||||
|
}).then((response) => response.json())
|
||||||
|
|
||||||
|
console.log('Data fetch is done', wordList)
|
||||||
|
|
||||||
|
return wordList
|
||||||
|
}
|
||||||
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
Loaded 100 of 295 files, more files were not shown because too many files have changed in this diff.
Show more
Reference in new issue
Block a user