✨(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:
2
.github/workflows/people.yml
vendored
2
.github/workflows/people.yml
vendored
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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: '^_' },
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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",
|
||||
|
||||
47
src/frontend/app/desk/src/app/Teams/api/useCreateTeam.tsx
Normal file
47
src/frontend/app/desk/src/app/Teams/api/useCreateTeam.tsx
Normal 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,
|
||||
});
|
||||
},
|
||||
});
|
||||
}
|
||||
47
src/frontend/app/desk/src/app/Teams/api/useTeams.tsx
Normal file
47
src/frontend/app/desk/src/app/Teams/api/useTeams.tsx
Normal 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,
|
||||
});
|
||||
}
|
||||
46
src/frontend/app/desk/src/app/Teams/index.tsx
Normal file
46
src/frontend/app/desk/src/app/Teams/index.tsx
Normal 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>
|
||||
</>
|
||||
);
|
||||
};
|
||||
@@ -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>
|
||||
);
|
||||
|
||||
@@ -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>
|
||||
);
|
||||
}
|
||||
|
||||
6
src/frontend/app/desk/src/types/api.ts
Normal file
6
src/frontend/app/desk/src/types/api.ts
Normal file
@@ -0,0 +1,6 @@
|
||||
export interface APIList<T> {
|
||||
count: number;
|
||||
next?: string | null;
|
||||
previous?: string | null;
|
||||
results: T[];
|
||||
}
|
||||
@@ -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==
|
||||
|
||||
Reference in New Issue
Block a user