✨(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:
|
build-front-desk:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
|
env:
|
||||||
|
NEXT_PUBLIC_API_URL: http://localhost:8071/api/v1.0/
|
||||||
defaults:
|
defaults:
|
||||||
run:
|
run:
|
||||||
working-directory: src/frontend/app/desk
|
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_URL=http://localhost:8080/
|
||||||
NEXT_PUBLIC_KEYCLOAK_REALM=people
|
NEXT_PUBLIC_KEYCLOAK_REALM=people
|
||||||
NEXT_PUBLIC_KEYCLOAK_CLIENT_ID=people-front
|
NEXT_PUBLIC_KEYCLOAK_CLIENT_ID=people-front
|
||||||
|
|||||||
@@ -1,5 +1,9 @@
|
|||||||
module.exports = {
|
module.exports = {
|
||||||
extends: ['next', 'plugin:prettier/recommended'],
|
extends: [
|
||||||
|
'next',
|
||||||
|
'plugin:prettier/recommended',
|
||||||
|
'plugin:@tanstack/eslint-plugin-query/recommended',
|
||||||
|
],
|
||||||
settings: {
|
settings: {
|
||||||
next: {
|
next: {
|
||||||
rootDir: __dirname,
|
rootDir: __dirname,
|
||||||
@@ -12,7 +16,6 @@ module.exports = {
|
|||||||
},
|
},
|
||||||
rules: {
|
rules: {
|
||||||
'block-scoped-var': 'error',
|
'block-scoped-var': 'error',
|
||||||
'no-alert': 'error',
|
|
||||||
'import/no-duplicates': ['error', { considerQueryString: false }],
|
'import/no-duplicates': ['error', { considerQueryString: false }],
|
||||||
'import/order': [
|
'import/order': [
|
||||||
'error',
|
'error',
|
||||||
@@ -38,6 +41,7 @@ module.exports = {
|
|||||||
'newlines-between': 'always',
|
'newlines-between': 'always',
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
|
'no-alert': 'error',
|
||||||
'no-unused-vars': [
|
'no-unused-vars': [
|
||||||
'error',
|
'error',
|
||||||
{ varsIgnorePattern: '^_', argsIgnorePattern: '^_' },
|
{ varsIgnorePattern: '^_', argsIgnorePattern: '^_' },
|
||||||
|
|||||||
@@ -1,4 +1,14 @@
|
|||||||
/** @type {import('next').NextConfig} */
|
/** @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;
|
module.exports = nextConfig;
|
||||||
|
|||||||
@@ -15,12 +15,15 @@
|
|||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@openfun/cunningham-react": "2.4.0",
|
"@openfun/cunningham-react": "2.4.0",
|
||||||
|
"@tanstack/react-query": "5.17.1",
|
||||||
"next": "14.0.4",
|
"next": "14.0.4",
|
||||||
"react": "18.2.0",
|
"react": "18.2.0",
|
||||||
"react-dom": "18.2.0",
|
"react-dom": "18.2.0",
|
||||||
"zustand": "4.4.7"
|
"zustand": "4.4.7"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
|
"@tanstack/eslint-plugin-query": "5.17.1",
|
||||||
|
"@tanstack/react-query-devtools": "5.17.12",
|
||||||
"@testing-library/jest-dom": "6.2.0",
|
"@testing-library/jest-dom": "6.2.0",
|
||||||
"@testing-library/react": "14.1.2",
|
"@testing-library/react": "14.1.2",
|
||||||
"@types/jest": "29.5.11",
|
"@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';
|
'use client';
|
||||||
|
|
||||||
import { CunninghamProvider, Switch } from '@openfun/cunningham-react';
|
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 { Inter } from 'next/font/google';
|
||||||
import { useState } from 'react';
|
import { useState } from 'react';
|
||||||
|
|
||||||
import './globals.css';
|
import './globals.css';
|
||||||
|
|
||||||
const inter = Inter({ subsets: ['latin'] });
|
const inter = Inter({ subsets: ['latin'] });
|
||||||
|
const queryClient = new QueryClient();
|
||||||
|
|
||||||
export default function RootLayout({
|
export default function RootLayout({
|
||||||
children,
|
children,
|
||||||
@@ -18,20 +20,23 @@ export default function RootLayout({
|
|||||||
return (
|
return (
|
||||||
<html lang="en">
|
<html lang="en">
|
||||||
<body className={inter.className}>
|
<body className={inter.className}>
|
||||||
<CunninghamProvider theme={themeDark ? 'dark' : 'default'}>
|
<QueryClientProvider client={queryClient}>
|
||||||
<div
|
<ReactQueryDevtools />
|
||||||
style={{
|
<CunninghamProvider theme={themeDark ? 'dark' : 'default'}>
|
||||||
backgroundColor: themeDark ? '#555' : 'white',
|
<div
|
||||||
}}
|
style={{
|
||||||
>
|
backgroundColor: themeDark ? '#555' : 'white',
|
||||||
<Switch
|
}}
|
||||||
label="Dark"
|
>
|
||||||
onChange={() => setThemeDark(!themeDark)}
|
<Switch
|
||||||
checked={themeDark}
|
label="Dark"
|
||||||
/>
|
onChange={() => setThemeDark(!themeDark)}
|
||||||
{children}
|
checked={themeDark}
|
||||||
</div>
|
/>
|
||||||
</CunninghamProvider>
|
{children}
|
||||||
|
</div>
|
||||||
|
</CunninghamProvider>
|
||||||
|
</QueryClientProvider>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -1,12 +1,13 @@
|
|||||||
'use client';
|
'use client';
|
||||||
|
|
||||||
import { Button, Loader } from '@openfun/cunningham-react';
|
import { Loader } from '@openfun/cunningham-react';
|
||||||
import { useEffect } from 'react';
|
import { useEffect } from 'react';
|
||||||
|
|
||||||
import styles from './page.module.css';
|
|
||||||
|
|
||||||
import useAuthStore from '@/auth/useAuthStore';
|
import useAuthStore from '@/auth/useAuthStore';
|
||||||
|
|
||||||
|
import { Teams } from './Teams';
|
||||||
|
import styles from './page.module.css';
|
||||||
|
|
||||||
export default function Home() {
|
export default function Home() {
|
||||||
const { initAuth, authenticated } = useAuthStore();
|
const { initAuth, authenticated } = useAuthStore();
|
||||||
|
|
||||||
@@ -21,7 +22,7 @@ export default function Home() {
|
|||||||
return (
|
return (
|
||||||
<main className={styles.main}>
|
<main className={styles.main}>
|
||||||
<h2>Hello world!</h2>
|
<h2>Hello world!</h2>
|
||||||
<Button>Button Cunningham</Button>
|
<Teams />
|
||||||
</main>
|
</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:
|
dependencies:
|
||||||
tslib "^2.4.0"
|
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":
|
"@tanstack/react-table@8.10.7":
|
||||||
version "8.10.7"
|
version "8.10.7"
|
||||||
resolved "https://registry.yarnpkg.com/@tanstack/react-table/-/react-table-8.10.7.tgz#733f4bee8cf5aa19582f944dd0fd3224b21e8c94"
|
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"
|
"@typescript-eslint/typescript-estree" "6.18.1"
|
||||||
semver "^7.5.4"
|
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"
|
version "5.62.0"
|
||||||
resolved "https://registry.yarnpkg.com/@typescript-eslint/utils/-/utils-5.62.0.tgz#141e809c71636e4a75daa39faed2fb5f4b10df86"
|
resolved "https://registry.yarnpkg.com/@typescript-eslint/utils/-/utils-5.62.0.tgz#141e809c71636e4a75daa39faed2fb5f4b10df86"
|
||||||
integrity sha512-n8oxjeb5aIbPFEtmQxQYOLI0i9n5ySBEY/ZEHHZqKQSFnxio1rv6dthascc9dLuwrL0RC5mPCxB7vnAVGAYWAQ==
|
integrity sha512-n8oxjeb5aIbPFEtmQxQYOLI0i9n5ySBEY/ZEHHZqKQSFnxio1rv6dthascc9dLuwrL0RC5mPCxB7vnAVGAYWAQ==
|
||||||
|
|||||||
Reference in New Issue
Block a user