(app-desk) create a basic feature Teams

As a prove of concept, to check the full process of our token,
we create a basic feature Teams.
This feature can create a team and list all teams.
We use react-query to manage the cache and the request to the API.
This commit is contained in:
Anthony LC
2024-01-10 11:35:30 +01:00
committed by Anthony LC
parent d0b2f9c171
commit 5aca2c48e3
12 changed files with 226 additions and 23 deletions

View File

@@ -81,6 +81,8 @@ jobs:
build-front-desk:
runs-on: ubuntu-latest
env:
NEXT_PUBLIC_API_URL: http://localhost:8071/api/v1.0/
defaults:
run:
working-directory: src/frontend/app/desk

View File

@@ -1,3 +1,4 @@
NEXT_PUBLIC_API_URL=http://localhost:8071/api/v1.0/
NEXT_PUBLIC_KEYCLOAK_URL=http://localhost:8080/
NEXT_PUBLIC_KEYCLOAK_REALM=people
NEXT_PUBLIC_KEYCLOAK_CLIENT_ID=people-front

View File

@@ -1,5 +1,9 @@
module.exports = {
extends: ['next', 'plugin:prettier/recommended'],
extends: [
'next',
'plugin:prettier/recommended',
'plugin:@tanstack/eslint-plugin-query/recommended',
],
settings: {
next: {
rootDir: __dirname,
@@ -12,7 +16,6 @@ module.exports = {
},
rules: {
'block-scoped-var': 'error',
'no-alert': 'error',
'import/no-duplicates': ['error', { considerQueryString: false }],
'import/order': [
'error',
@@ -38,6 +41,7 @@ module.exports = {
'newlines-between': 'always',
},
],
'no-alert': 'error',
'no-unused-vars': [
'error',
{ varsIgnorePattern: '^_', argsIgnorePattern: '^_' },

View File

@@ -1,4 +1,14 @@
/** @type {import('next').NextConfig} */
const nextConfig = {};
const nextConfig = {
trailingSlash: true,
async rewrites() {
return [
{
source: '/api/:slug*/',
destination: `${process.env.NEXT_PUBLIC_API_URL}:slug*/`, // Matched parameters can be used in the destination
},
];
},
};
module.exports = nextConfig;

View File

@@ -15,12 +15,15 @@
},
"dependencies": {
"@openfun/cunningham-react": "2.4.0",
"@tanstack/react-query": "5.17.1",
"next": "14.0.4",
"react": "18.2.0",
"react-dom": "18.2.0",
"zustand": "4.4.7"
},
"devDependencies": {
"@tanstack/eslint-plugin-query": "5.17.1",
"@tanstack/react-query-devtools": "5.17.12",
"@testing-library/jest-dom": "6.2.0",
"@testing-library/react": "14.1.2",
"@types/jest": "29.5.11",

View File

@@ -0,0 +1,47 @@
import { useMutation, useQueryClient } from '@tanstack/react-query';
import useAuthStore from '@/auth/useAuthStore';
import { KEY_LIST_TEAM } from './useTeams';
type CreateTeamResponse = {
id: string;
name: string;
};
export interface CreateTeamResponseError {
detail: string;
}
export const createTeam = async (name: string) => {
const { token } = useAuthStore.getState();
const response = await fetch(`/api/teams/`, {
headers: {
'Content-Type': 'application/json',
Authorization: `Bearer ${token}`,
},
method: 'POST',
body: JSON.stringify({
name,
}),
});
if (!response.ok) {
throw new Error(`Couldn't create team: ${response.statusText}`);
}
return response.json();
};
export function useCreateTeam() {
const queryClient = useQueryClient();
return useMutation<CreateTeamResponse, CreateTeamResponseError, string>({
mutationFn: createTeam,
onSuccess: () => {
void queryClient.invalidateQueries({
queryKey: [KEY_LIST_TEAM],
exact: true,
});
},
});
}

View File

@@ -0,0 +1,47 @@
import { UseQueryOptions, useQuery } from '@tanstack/react-query';
import useAuthStore from '@/auth/useAuthStore';
import { APIList } from '@/types/api';
interface TeamResponse {
id: string;
name: string;
}
type TeamsResponse = APIList<TeamResponse>;
export interface TeamsResponseError {
detail: string;
}
export const getTeams = async () => {
const token = useAuthStore.getState().token;
const response = await fetch(`/api/teams/`, {
headers: {
'Content-Type': 'application/json',
Authorization: `Bearer ${token}`,
},
});
if (!response.ok) {
throw new Error(`Couldn't fetch teams: ${response.statusText}`);
}
return response.json();
};
export const KEY_LIST_TEAM = 'teams';
export function useTeams(
queryConfig?: UseQueryOptions<
TeamsResponse,
TeamsResponseError,
TeamsResponse
>,
) {
return useQuery<TeamsResponse, TeamsResponseError, TeamsResponse>({
queryKey: [KEY_LIST_TEAM],
queryFn: getTeams,
...queryConfig,
});
}

View File

@@ -0,0 +1,46 @@
import { Button, Field, Input, Loader } from '@openfun/cunningham-react';
import React, { useState } from 'react';
import { useCreateTeam } from './api/useCreateTeam';
import { useTeams } from './api/useTeams';
export const Teams = () => {
const { data, isPending } = useTeams();
const { mutate: createTeam } = useCreateTeam();
const [teamName, setTeamName] = useState('');
if (isPending)
return (
<div>
<Loader />
</div>
);
return (
<>
<Field>
<Input
type="text"
label="Team name"
onChange={(e) => setTeamName(e.target.value)}
/>
<Button fullWidth onClick={() => createTeam(teamName)} className="mt-s">
Create Team
</Button>
</Field>
<section>
<ul>
{data?.results.map((post, index) => (
<li key={post.id}>
<div>
<span>
{index + 1}. {post.name}
</span>
</div>
</li>
))}
</ul>
</section>
</>
);
};

View File

@@ -1,12 +1,14 @@
'use client';
import { CunninghamProvider, Switch } from '@openfun/cunningham-react';
import { QueryClient, QueryClientProvider } from '@tanstack/react-query';
import { ReactQueryDevtools } from '@tanstack/react-query-devtools';
import { Inter } from 'next/font/google';
import { useState } from 'react';
import './globals.css';
const inter = Inter({ subsets: ['latin'] });
const queryClient = new QueryClient();
export default function RootLayout({
children,
@@ -18,20 +20,23 @@ export default function RootLayout({
return (
<html lang="en">
<body className={inter.className}>
<CunninghamProvider theme={themeDark ? 'dark' : 'default'}>
<div
style={{
backgroundColor: themeDark ? '#555' : 'white',
}}
>
<Switch
label="Dark"
onChange={() => setThemeDark(!themeDark)}
checked={themeDark}
/>
{children}
</div>
</CunninghamProvider>
<QueryClientProvider client={queryClient}>
<ReactQueryDevtools />
<CunninghamProvider theme={themeDark ? 'dark' : 'default'}>
<div
style={{
backgroundColor: themeDark ? '#555' : 'white',
}}
>
<Switch
label="Dark"
onChange={() => setThemeDark(!themeDark)}
checked={themeDark}
/>
{children}
</div>
</CunninghamProvider>
</QueryClientProvider>
</body>
</html>
);

View File

@@ -1,12 +1,13 @@
'use client';
import { Button, Loader } from '@openfun/cunningham-react';
import { Loader } from '@openfun/cunningham-react';
import { useEffect } from 'react';
import styles from './page.module.css';
import useAuthStore from '@/auth/useAuthStore';
import { Teams } from './Teams';
import styles from './page.module.css';
export default function Home() {
const { initAuth, authenticated } = useAuthStore();
@@ -21,7 +22,7 @@ export default function Home() {
return (
<main className={styles.main}>
<h2>Hello world!</h2>
<Button>Button Cunningham</Button>
<Teams />
</main>
);
}

View File

@@ -0,0 +1,6 @@
export interface APIList<T> {
count: number;
next?: string | null;
previous?: string | null;
results: T[];
}

View File

@@ -1970,6 +1970,37 @@
dependencies:
tslib "^2.4.0"
"@tanstack/eslint-plugin-query@5.17.1":
version "5.17.1"
resolved "https://registry.yarnpkg.com/@tanstack/eslint-plugin-query/-/eslint-plugin-query-5.17.1.tgz#e029ae3a2c35ba915e81c50e210a6138e7dd3aea"
integrity sha512-KWag75cTqs+/t3DSuoyM1Vh9Ns4FOnXZOkCn8nkxvOWnHRBL073c9Kg3YiYP3uo8ftcb1iiw3+a/5w1bf+8A7A==
dependencies:
"@typescript-eslint/utils" "^5.62.0"
"@tanstack/query-core@5.17.1":
version "5.17.1"
resolved "https://registry.yarnpkg.com/@tanstack/query-core/-/query-core-5.17.1.tgz#341d975dce20c5c9e0767a3a69c0bfbc9ca16114"
integrity sha512-kUXozQmU7NBtzX5dM6qfFNZN+YK/9Ct37hnG/ogdgI4mExIx7VH/qRepsPhKfNrJz2w81/JykmM3Uug6sVpUSw==
"@tanstack/query-devtools@5.17.7":
version "5.17.7"
resolved "https://registry.yarnpkg.com/@tanstack/query-devtools/-/query-devtools-5.17.7.tgz#9f9e44a32d08ecd5c9fe3ede62a114d6d6e240d5"
integrity sha512-TfgvOqza5K7Sk6slxqkRIvXlEJoUoPSsGGwpuYSrpqgSwLSSvPPpZhq7hv7hcY5IvRoTNGoq6+MT01C/jILqoQ==
"@tanstack/react-query-devtools@5.17.12":
version "5.17.12"
resolved "https://registry.yarnpkg.com/@tanstack/react-query-devtools/-/react-query-devtools-5.17.12.tgz#61b62a230472243931982d0c802d1a1bc164fc5d"
integrity sha512-5n2oqe9GUD7+QNJqfFm8RqQVVFOeaLQDujBnhyyILDq8XPB6wymTEaS6OJ/CdrhCYKoQe3Uh8E6u9ChABEMuhA==
dependencies:
"@tanstack/query-devtools" "5.17.7"
"@tanstack/react-query@5.17.1":
version "5.17.1"
resolved "https://registry.yarnpkg.com/@tanstack/react-query/-/react-query-5.17.1.tgz#37efbb4573d3f4cc8c62e5ea6c7398d27f52db2a"
integrity sha512-4JYgX0kU+pvwVQi5eRiHGvBK7WnahEl6lmaxd32ZVSKmByAxLgaewoxBR03cdDNse8lUD2zGOe0sx3M/EGRlmA==
dependencies:
"@tanstack/query-core" "5.17.1"
"@tanstack/react-table@8.10.7":
version "8.10.7"
resolved "https://registry.yarnpkg.com/@tanstack/react-table/-/react-table-8.10.7.tgz#733f4bee8cf5aa19582f944dd0fd3224b21e8c94"
@@ -2299,7 +2330,7 @@
"@typescript-eslint/typescript-estree" "6.18.1"
semver "^7.5.4"
"@typescript-eslint/utils@^5.10.0", "@typescript-eslint/utils@^5.58.0":
"@typescript-eslint/utils@^5.10.0", "@typescript-eslint/utils@^5.58.0", "@typescript-eslint/utils@^5.62.0":
version "5.62.0"
resolved "https://registry.yarnpkg.com/@typescript-eslint/utils/-/utils-5.62.0.tgz#141e809c71636e4a75daa39faed2fb5f4b10df86"
integrity sha512-n8oxjeb5aIbPFEtmQxQYOLI0i9n5ySBEY/ZEHHZqKQSFnxio1rv6dthascc9dLuwrL0RC5mPCxB7vnAVGAYWAQ==