updating api

This commit is contained in:
2024-10-08 16:40:43 +05:00
parent f69fc86a01
commit 5fd09dbdab
25 changed files with 504 additions and 43 deletions
+1 -1
View File
@@ -1 +1 @@
NEXT_PUBLIC_API=https://graff.estate/api
NEXT_PUBLIC_API=http://localhost:3001/
+1
View File
@@ -18,6 +18,7 @@
"date-fns": "^3.6.0",
"framer-motion": "^11.3.9",
"graphql": "^16.9.0",
"graphql-scalars": "^1.23.0",
"ky": "^1.4.0",
"libphonenumber-js": "^1.11.7",
"next": "14.2.5",
@@ -0,0 +1,7 @@
export default function BlogLayout({
children,
}: {
children: React.ReactNode;
}) {
return <section>{children}</section>;
}
+3
View File
@@ -0,0 +1,3 @@
export default function DashboardBlogPage() {
return <div></div>;
}
+7
View File
@@ -0,0 +1,7 @@
export default function DashboardLayout({
children,
}: {
children: React.ReactNode;
}) {
return <section>{children}</section>;
}
+3
View File
@@ -0,0 +1,3 @@
export default function DashboardPage() {
return <div></div>;
}
@@ -0,0 +1,7 @@
export default function ProjectsLayout({
children,
}: {
children: React.ReactNode;
}) {
return <section>{children}</section>;
}
@@ -0,0 +1,9 @@
import { AddProject } from '@/components/AddProject';
export default function DashboardProjectsPage() {
return (
<div>
<AddProject />
</div>
);
}
+40
View File
@@ -0,0 +1,40 @@
'use client';
import { useCheckAuthQuery } from '@/utils/checkAuth/checkAuth.generated';
import { useLogoutLazyQuery } from '@/utils/logout/logout.generated';
import { useApolloClient } from '@apollo/client';
import { useRouter } from 'next/navigation';
import { useEffect } from 'react';
export default function AdminLayout({
children,
}: {
children: React.ReactNode;
}) {
const router = useRouter();
const client = useApolloClient();
const { data } = useCheckAuthQuery({});
const [logout] = useLogoutLazyQuery({
onCompleted: () => {
client.refetchQueries({ include: ['CheckAuth'] });
},
});
useEffect(() => {
if (
data?.checkAuth.__typename !== 'CheckAuthResponse' ||
!data.checkAuth.isAuth
) {
router.replace('/login');
}
}, [data, router]);
return (
<section>
<button onClick={() => logout()}>выйти</button>
{children}
</section>
);
}
+14 -10
View File
@@ -1,6 +1,8 @@
import { useAuthStore } from '@/stores/useAuthStore';
'use client';
import { useCheckAuthQuery } from '@/utils/checkAuth/checkAuth.generated';
import { useRouter } from 'next/navigation';
import { useEffect } from 'react';
export default function AuthLayout({
children,
@@ -8,14 +10,16 @@ export default function AuthLayout({
children: React.ReactNode;
}) {
const router = useRouter();
const { token } = useAuthStore();
const { data } = useCheckAuthQuery({
context: { headers: { Authorization: `Bearer ${token}` } },
});
if (
data?.checkAuth.__typename === 'CheckAuthResponse' &&
data.checkAuth.isAuth
)
router.replace('/');
const { data } = useCheckAuthQuery();
useEffect(() => {
// if (
// data?.checkAuth.__typename === 'CheckAuthResponse' &&
// data.checkAuth.isAuth
// )
// router.replace('/dashboard');
}, [data, router]);
return <>{children}</>;
}
+4 -7
View File
@@ -1,19 +1,16 @@
import { AvailablesSlider } from '@/components/pages/MainPage/Availables/AvailablesSlider';
import { Calculator } from '@/components/pages/MainPage/Calculator/Calculator';
import { Clients } from '@/components/pages/MainPage/Clients';
import { Datamining } from '@/components/pages/MainPage/Datamining';
import { IntegrationsSlider } from '@/components/pages/MainPage/Integrations/IntegrationsSlider';
import { Motivation } from '@/components/pages/MainPage/Motivation';
import { Projects } from '@/components/pages/MainPage/Projects';
import { Reviews } from '@/components/pages/MainPage/Reviews';
import { Showreel } from '@/components/pages/MainPage/Showreel';
import { Statistics } from '@/components/pages/MainPage/Statistics';
import { Streaming } from '@/components/pages/MainPage/Streaming';
import { Winners } from '@/components/pages/MainPage/Winners';
import { integrations } from '@/consts/integrations';
import dynamic from 'next/dynamic';
export default function Home() {
export default function HomePage() {
const DynamicEllipse = dynamic(
() => import('@/components/Layout/Ellipse').then(m => m.Ellipse),
{
@@ -26,7 +23,7 @@ export default function Home() {
<DynamicEllipse />
<Motivation />
<Showreel />
<Statistics />
{/* <Statistics /> */}
<IntegrationsSlider integrations={integrations} />
<AvailablesSlider
availables={[
@@ -49,10 +46,10 @@ export default function Home() {
/>
<Streaming />
<Datamining />
<Calculator />
{/* <Calculator /> */}
<Reviews />
<Winners />
<Projects />
{/* <Projects /> */}
<Clients />
</>
);
@@ -0,0 +1,58 @@
import * as Types from '../../generated/graphql';
import { gql } from '@apollo/client';
import * as Apollo from '@apollo/client';
const defaultOptions = {} as const;
export type AddProjectMutationVariables = Types.Exact<{
input: Types.CreateProjectInput;
}>;
export type AddProjectMutation = { __typename?: 'Mutation', createProject: { __typename?: 'Error', message: string } | { __typename?: 'Project', id: number, name: string, company: string, city: string, image: string, stage: number, releaseDate: any, devices: Array<string> } };
export const AddProjectDocument = gql`
mutation AddProject($input: CreateProjectInput!) {
createProject(input: $input) {
... on Error {
message
}
... on Project {
id
name
company
city
image
stage
releaseDate
devices
}
}
}
`;
export type AddProjectMutationFn = Apollo.MutationFunction<AddProjectMutation, AddProjectMutationVariables>;
/**
* __useAddProjectMutation__
*
* To run a mutation, you first call `useAddProjectMutation` within a React component and pass it any options that fit your needs.
* When your component renders, `useAddProjectMutation` returns a tuple that includes:
* - A mutate function that you can call at any time to execute the mutation
* - An object with fields that represent the current status of the mutation's execution
*
* @param baseOptions options that will be passed into the mutation, supported options are listed on: https://www.apollographql.com/docs/react/api/react-hooks/#options-2;
*
* @example
* const [addProjectMutation, { data, loading, error }] = useAddProjectMutation({
* variables: {
* input: // value for 'input'
* },
* });
*/
export function useAddProjectMutation(baseOptions?: Apollo.MutationHookOptions<AddProjectMutation, AddProjectMutationVariables>) {
const options = {...defaultOptions, ...baseOptions}
return Apollo.useMutation<AddProjectMutation, AddProjectMutationVariables>(AddProjectDocument, options);
}
export type AddProjectMutationHookResult = ReturnType<typeof useAddProjectMutation>;
export type AddProjectMutationResult = Apollo.MutationResult<AddProjectMutation>;
export type AddProjectMutationOptions = Apollo.BaseMutationOptions<AddProjectMutation, AddProjectMutationVariables>;
+17
View File
@@ -0,0 +1,17 @@
mutation AddProject($input: CreateProjectInput!) {
createProject(input: $input) {
... on Error {
message
}
... on Project {
id
name
company
city
image
stage
releaseDate
devices
}
}
}
+40
View File
@@ -0,0 +1,40 @@
'use client';
import { Device } from '@/types/IProject';
import { Button } from '@/ui/Button';
import { useState } from 'react';
import { useForm } from 'react-hook-form';
import { ProjectFormModal } from '../Layout/ProjectFormModal';
import { useAddProjectMutation } from './addProject.generated';
export function AddProject() {
const [open, setOpen] = useState(false);
const [addProject] = useAddProjectMutation();
interface IAddProjectFormInput {
name: string;
company: string;
city: string;
image: string;
stage: 1 | 2 | 3 | 4 | 5;
releaseDate: string;
devices: Device[];
}
const { register, handleSubmit } = useForm<IAddProjectFormInput>({
defaultValues: { devices: [], stage: 1 },
});
return (
<>
<Button onClick={() => setOpen(true)}>Добавить проект</Button>
{open && (
<ProjectFormModal
action={'create'}
onSubmit={function (data: IAddProjectFormInput) {}}
/>
)}
</>
);
}
+114
View File
@@ -0,0 +1,114 @@
import { api } from '@/api';
import { Device } from '@/types/IProject';
import Image from 'next/image';
import { ChangeEvent, useState } from 'react';
import { useForm } from 'react-hook-form';
interface IAddProjectFormInput {
name: string;
company: string;
city: string;
image: string;
stage: 1 | 2 | 3 | 4 | 5;
releaseDate: string;
devices: Device[];
}
interface IProjectFormModalProps {
action: 'create' | 'edit';
onSubmit: (data: IAddProjectFormInput) => void;
defaultValues?: IAddProjectFormInput;
}
export function ProjectFormModal({ onSubmit, action }: IProjectFormModalProps) {
const { register, handleSubmit, setValue } = useForm<IAddProjectFormInput>({
defaultValues: { devices: [], stage: 1 },
});
const [file, setFile] = useState<File>();
const [previewFile, setPreviewFile] = useState<string>();
function handleChangeFile(e: ChangeEvent<HTMLInputElement>) {
if (!e.target.files) return;
const targetFile = e.target.files[0];
setFile(targetFile);
setPreviewFile(URL.createObjectURL(targetFile));
}
async function uploadFile() {
if (!file) return;
const formData = new FormData();
formData.append('dest', 'projects');
formData.append('files', file);
try {
// const { file }: { file: string } = await api
// .post('upload', { body: formData })
// .json();
// setProject((prev) => ({
// ...prev,
// image: file,
// }));
const { files }: { files: string[] } = await api
.post('upload', { body: formData })
.json();
setValue('image', files[0]);
console.log(files);
} catch (error) {
if (error instanceof Error) {
alert(`Error: ${error.message}`);
}
}
}
return (
<div>
<form onSubmit={handleSubmit(onSubmit)}>
<div className="grid grid-cols-2 gap-4">
<label htmlFor="name">Название</label>
<input type="text" {...register('name')} />
<label htmlFor="company">Компания</label>
<input type="text" {...register('company')} />
<label htmlFor="city">Город</label>
<input type="text" {...register('city')} />
<label className="relative border border-dashed border-neutral-500 px-3 py-2 hover:bg-opacity-10 hover:bg-black cursor-pointer rounded-lg flex flex-col gap-2">
<input
required
type="file"
accept="image/*"
className="absolute opacity-0"
onChange={handleChangeFile}
/>
<p className="truncate">
{file ? file.name : 'Выберите изображение'}
</p>
{previewFile && <Image src={previewFile} alt="" />}
</label>
<label htmlFor="stage">Стадия</label>
<select {...register('stage')}>
{Array.from({ length: 5 }, (_, i) => i + 1).map(stage => (
<option key={stage} value={stage} label={stage.toString()} />
))}
</select>
<label htmlFor="releaseDate">Дата релиза</label>
<input type="date" {...register('releaseDate')} />
<label htmlFor="devices">Устройства</label>
{['Stream', 'Touch', 'Mobile', 'VR'].map(device => (
<input
type="checkbox"
{...register('devices')}
value={device}
key={device}
/>
))}
</div>
<button>
{action === 'create' ? 'Добавить проект' : 'Сохранить изменения'}
</button>
</form>
</div>
);
}
+5 -4
View File
@@ -1,6 +1,6 @@
'use client';
import { useAuthStore } from '@/stores/useAuthStore';
import { useApolloClient } from '@apollo/client';
import { SubmitHandler, useForm } from 'react-hook-form';
import { useLoginMutation } from './login.generated';
@@ -11,9 +11,10 @@ interface ILoginFormInput {
export function LoginForm() {
const [login] = useLoginMutation();
const { register, handleSubmit } = useForm<ILoginFormInput>();
const { token, setToken } = useAuthStore();
const client = useApolloClient();
const { register, handleSubmit } = useForm<ILoginFormInput>();
const onSubmit: SubmitHandler<ILoginFormInput> = async ({
password,
@@ -23,7 +24,7 @@ export function LoginForm() {
variables: { password, username },
onCompleted: data => {
if (data?.login.__typename === 'AuthResponse')
setToken(data.login.accessToken);
client.refetchQueries({ include: ['CheckAuth'] });
},
});
+6 -3
View File
@@ -1,8 +1,8 @@
'use client';
import { api } from '@/api';
import { ArrowMoreIcon } from '@/components/icons/ArrowMoreIcon';
import { ClassNameWrapper } from '@/hocs/ClassNameWrapper';
import { useGetProjectsQuery } from '@/queries/getProjects/getProjects.generated';
import { IProject } from '@/types/IProject';
import { Button } from '@/ui/Button';
import { Descriptor } from '@/ui/Descriptor';
@@ -17,10 +17,13 @@ export function Projects() {
const [sortedProjects, setSortedProjects] =
useState<Map<string | number, IProject[]>>();
const { data } = useGetProjectsQuery({ variables: { devices: [] } });
async function getProjects() {
if (!data) return;
try {
const projects: IProject[] = await api.get('projects').json();
setSortedProjects(getSortedProjects(projects));
// releaseDate Date хз
setSortedProjects(getSortedProjects([]));
} catch (error) {
if (error instanceof Error) {
alert(`Error: ${error.message}`);
+2 -2
View File
@@ -4,7 +4,6 @@ import { api } from '@/api';
import { IProject } from '@/types/IProject';
import { Descriptor } from '@/ui/Descriptor';
import { Title } from '@/ui/Title';
import { getProjectsCount } from '@/utils/getProjectsCount';
import { motion, useInView, useMotionValue, useSpring } from 'framer-motion';
import { Manrope } from 'next/font/google';
import Image from 'next/image';
@@ -45,7 +44,8 @@ export function Statistics() {
<div className="grid lg:grid-cols-12 grid-cols-2 border-t border-[#3D425C]">
<div className="lg:col-span-3 col-span-2 lg:pt-10 sm:py-8 py-4 lg:border-r border-b border-[#3D425C] accent font-medium">
Мы собрали статистику за&nbsp;13&nbsp;лет работы c&nbsp;застройщиками,
реализовав {getProjectsCount(projects.length)}
реализовав
{/* {getProjectsCount(projects.length)} */}
</div>
<div className="lg:col-start-4 lg:col-span-full sm:col-span-2 lg:py-10 lg:pl-4 border-b border-[#3D425C] aspect-[1176/570] max-md:hidden">
<ProjectsMap />
+20 -1
View File
@@ -4,10 +4,29 @@ import {
InMemoryCache,
SSRMultipartLink,
} from '@apollo/experimental-nextjs-app-support';
import { DateTypeDefinition } from 'graphql-scalars';
export function makeClient() {
const httpLink = new HttpLink({ uri: 'http://localhost:3001/graphql' });
const httpLink = new HttpLink({
uri: 'http://localhost:3001/graphql',
credentials: 'include',
});
return new ApolloClient({
// typeDefs: {
// definitions: [DateTypeDefinition],
// },
// resolvers: [
// {
// Date: DateResolver,
// },
// ],
typeDefs: [DateTypeDefinition],
resolvers: {
Date: {
__resolveType: () => 'Date',
},
},
cache: new InMemoryCache(),
link:
typeof window === 'undefined'
@@ -0,0 +1,60 @@
import * as Types from '../../generated/graphql';
import { gql } from '@apollo/client';
import * as Apollo from '@apollo/client';
const defaultOptions = {} as const;
export type GetProjectsQueryVariables = Types.Exact<{
devices: Array<Types.Scalars['String']['input']> | Types.Scalars['String']['input'];
}>;
export type GetProjectsQuery = { __typename?: 'Query', projects: Array<{ __typename?: 'Project', id: number, name: string, company: string, city: string, image: string, stage: number, releaseDate: any, devices: Array<string> }> };
export const GetProjectsDocument = gql`
query GetProjects($devices: [String!]!) {
projects(input: {devices: $devices}) {
id
name
company
city
image
stage
releaseDate
devices
}
}
`;
/**
* __useGetProjectsQuery__
*
* To run a query within a React component, call `useGetProjectsQuery` and pass it any options that fit your needs.
* When your component renders, `useGetProjectsQuery` returns an object from Apollo Client that contains loading, error, and data properties
* you can use to render your UI.
*
* @param baseOptions options that will be passed into the query, supported options are listed on: https://www.apollographql.com/docs/react/api/react-hooks/#options;
*
* @example
* const { data, loading, error } = useGetProjectsQuery({
* variables: {
* devices: // value for 'devices'
* },
* });
*/
export function useGetProjectsQuery(baseOptions: Apollo.QueryHookOptions<GetProjectsQuery, GetProjectsQueryVariables> & ({ variables: GetProjectsQueryVariables; skip?: boolean; } | { skip: boolean; }) ) {
const options = {...defaultOptions, ...baseOptions}
return Apollo.useQuery<GetProjectsQuery, GetProjectsQueryVariables>(GetProjectsDocument, options);
}
export function useGetProjectsLazyQuery(baseOptions?: Apollo.LazyQueryHookOptions<GetProjectsQuery, GetProjectsQueryVariables>) {
const options = {...defaultOptions, ...baseOptions}
return Apollo.useLazyQuery<GetProjectsQuery, GetProjectsQueryVariables>(GetProjectsDocument, options);
}
export function useGetProjectsSuspenseQuery(baseOptions?: Apollo.SkipToken | Apollo.SuspenseQueryHookOptions<GetProjectsQuery, GetProjectsQueryVariables>) {
const options = baseOptions === Apollo.skipToken ? baseOptions : {...defaultOptions, ...baseOptions}
return Apollo.useSuspenseQuery<GetProjectsQuery, GetProjectsQueryVariables>(GetProjectsDocument, options);
}
export type GetProjectsQueryHookResult = ReturnType<typeof useGetProjectsQuery>;
export type GetProjectsLazyQueryHookResult = ReturnType<typeof useGetProjectsLazyQuery>;
export type GetProjectsSuspenseQueryHookResult = ReturnType<typeof useGetProjectsSuspenseQuery>;
export type GetProjectsQueryResult = Apollo.QueryResult<GetProjectsQuery, GetProjectsQueryVariables>;
+14
View File
@@ -0,0 +1,14 @@
scalar Date
query GetProjects($devices: [String!]!) {
projects(input: { devices: $devices }) {
id
name
company
city
image
stage
releaseDate
devices
}
}
-15
View File
@@ -1,15 +0,0 @@
import { create } from 'zustand';
import { persist } from 'zustand/middleware';
export const useAuthStore = create<{
token: string;
setToken: (token: string) => void;
}>()(
persist(
set => ({
token: '',
setToken: token => set({ token }),
}),
{ name: 'auth' },
),
);
+55
View File
@@ -0,0 +1,55 @@
import * as Types from '../../generated/graphql';
import { gql } from '@apollo/client';
import * as Apollo from '@apollo/client';
const defaultOptions = {} as const;
export type LogoutQueryVariables = Types.Exact<{ [key: string]: never; }>;
export type LogoutQuery = { __typename?: 'Query', logout: { __typename?: 'Error', message: string } | { __typename?: 'LogoutResponse', success: boolean } };
export const LogoutDocument = gql`
query Logout {
logout {
... on Error {
message
}
... on LogoutResponse {
success
}
}
}
`;
/**
* __useLogoutQuery__
*
* To run a query within a React component, call `useLogoutQuery` and pass it any options that fit your needs.
* When your component renders, `useLogoutQuery` returns an object from Apollo Client that contains loading, error, and data properties
* you can use to render your UI.
*
* @param baseOptions options that will be passed into the query, supported options are listed on: https://www.apollographql.com/docs/react/api/react-hooks/#options;
*
* @example
* const { data, loading, error } = useLogoutQuery({
* variables: {
* },
* });
*/
export function useLogoutQuery(baseOptions?: Apollo.QueryHookOptions<LogoutQuery, LogoutQueryVariables>) {
const options = {...defaultOptions, ...baseOptions}
return Apollo.useQuery<LogoutQuery, LogoutQueryVariables>(LogoutDocument, options);
}
export function useLogoutLazyQuery(baseOptions?: Apollo.LazyQueryHookOptions<LogoutQuery, LogoutQueryVariables>) {
const options = {...defaultOptions, ...baseOptions}
return Apollo.useLazyQuery<LogoutQuery, LogoutQueryVariables>(LogoutDocument, options);
}
export function useLogoutSuspenseQuery(baseOptions?: Apollo.SkipToken | Apollo.SuspenseQueryHookOptions<LogoutQuery, LogoutQueryVariables>) {
const options = baseOptions === Apollo.skipToken ? baseOptions : {...defaultOptions, ...baseOptions}
return Apollo.useSuspenseQuery<LogoutQuery, LogoutQueryVariables>(LogoutDocument, options);
}
export type LogoutQueryHookResult = ReturnType<typeof useLogoutQuery>;
export type LogoutLazyQueryHookResult = ReturnType<typeof useLogoutLazyQuery>;
export type LogoutSuspenseQueryHookResult = ReturnType<typeof useLogoutSuspenseQuery>;
export type LogoutQueryResult = Apollo.QueryResult<LogoutQuery, LogoutQueryVariables>;
+10
View File
@@ -0,0 +1,10 @@
query Logout {
logout {
... on Error {
message
}
... on LogoutResponse {
success
}
}
}
+7
View File
@@ -3173,6 +3173,13 @@ graphql-request@^6.0.0:
"@graphql-typed-document-node/core" "^3.2.0"
cross-fetch "^3.1.5"
graphql-scalars@^1.23.0:
version "1.23.0"
resolved "https://registry.yarnpkg.com/graphql-scalars/-/graphql-scalars-1.23.0.tgz#486785d1a6f9449277054a92afc7e1fb73f459d6"
integrity sha512-YTRNcwitkn8CqYcleKOx9IvedA8JIERn8BRq21nlKgOr4NEcTaWEG0sT+H92eF3ALTFbPgsqfft4cw+MGgv0Gg==
dependencies:
tslib "^2.5.0"
graphql-tag@^2.11.0, graphql-tag@^2.12.6:
version "2.12.6"
resolved "https://registry.yarnpkg.com/graphql-tag/-/graphql-tag-2.12.6.tgz#d441a569c1d2537ef10ca3d1633b48725329b5f1"