Skip to content

Commit 1a0e8d1

Browse files
committed
all code added.
1 parent fbbc5d7 commit 1a0e8d1

File tree

11 files changed

+3401
-152
lines changed

11 files changed

+3401
-152
lines changed

components/ClientOnly.tsx

+20
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
import { ReactElement, useEffect, useState } from "react";
2+
3+
type Props = {
4+
children: ReactElement;
5+
};
6+
const ClientOnly: React.FC<Props> = ({ children, ...delegated }) => {
7+
const [hasMounted, setHasMounted] = useState(false);
8+
9+
useEffect(() => {
10+
setHasMounted(true);
11+
}, []);
12+
13+
if (!hasMounted) {
14+
return null;
15+
}
16+
17+
return <div {...delegated}>{children}</div>;
18+
};
19+
20+
export default ClientOnly;

components/EmailForm.tsx

+49
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
import { Character } from "@/generated/graphql";
2+
import { FormEvent, useState } from "react";
3+
4+
type Props = {
5+
characters: (Character | null)[] | null | undefined;
6+
};
7+
8+
const EmailForm: React.FC<Props> = ({ characters }) => {
9+
const [email, setEmail] = useState<string>("");
10+
11+
const onSubmit = async (e: FormEvent<HTMLFormElement>) => {
12+
e.preventDefault();
13+
if (characters && characters?.length > 0) {
14+
// Get random character from the array.
15+
const random = Math.floor(Math.random() * (characters.length - 1));
16+
const randomCharacter = characters[random];
17+
18+
console.log("email: ", email);
19+
console.log("name: ", randomCharacter?.name);
20+
console.log("species: ", randomCharacter?.species);
21+
22+
// Make send email request to /api/send-email
23+
fetch("http://localhost:3001/api/send-email", {
24+
method: "POST",
25+
body: JSON.stringify({
26+
email,
27+
name: randomCharacter?.name,
28+
species: randomCharacter?.species,
29+
}),
30+
});
31+
}
32+
};
33+
34+
return (
35+
<div>
36+
<p>Send me random character 🚀</p>
37+
<form onSubmit={onSubmit}>
38+
<input
39+
type="text"
40+
value={email}
41+
onChange={(e) => setEmail(e.target.value)}
42+
/>
43+
<button type="submit">Send!</button>
44+
</form>
45+
</div>
46+
);
47+
};
48+
49+
export default EmailForm;

generated/graphql.ts

+250
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,250 @@
1+
import { gql } from '@apollo/client';
2+
import * as Apollo from '@apollo/client';
3+
export type Maybe<T> = T | null;
4+
export type InputMaybe<T> = Maybe<T>;
5+
export type Exact<T extends { [key: string]: unknown }> = { [K in keyof T]: T[K] };
6+
export type MakeOptional<T, K extends keyof T> = Omit<T, K> & { [SubKey in K]?: Maybe<T[SubKey]> };
7+
export type MakeMaybe<T, K extends keyof T> = Omit<T, K> & { [SubKey in K]: Maybe<T[SubKey]> };
8+
const defaultOptions = {} as const;
9+
/** All built-in and custom scalars, mapped to their actual values */
10+
export type Scalars = {
11+
ID: string;
12+
String: string;
13+
Boolean: boolean;
14+
Int: number;
15+
Float: number;
16+
Upload: any;
17+
};
18+
19+
export enum CacheControlScope {
20+
Private = 'PRIVATE',
21+
Public = 'PUBLIC'
22+
}
23+
24+
export type Character = {
25+
__typename?: 'Character';
26+
/** Time at which the character was created in the database. */
27+
created?: Maybe<Scalars['String']>;
28+
/** Episodes in which this character appeared. */
29+
episode: Array<Maybe<Episode>>;
30+
/** The gender of the character ('Female', 'Male', 'Genderless' or 'unknown'). */
31+
gender?: Maybe<Scalars['String']>;
32+
/** The id of the character. */
33+
id?: Maybe<Scalars['ID']>;
34+
/**
35+
* Link to the character's image.
36+
* All images are 300x300px and most are medium shots or portraits since they are intended to be used as avatars.
37+
*/
38+
image?: Maybe<Scalars['String']>;
39+
/** The character's last known location */
40+
location?: Maybe<Location>;
41+
/** The name of the character. */
42+
name?: Maybe<Scalars['String']>;
43+
/** The character's origin location */
44+
origin?: Maybe<Location>;
45+
/** The species of the character. */
46+
species?: Maybe<Scalars['String']>;
47+
/** The status of the character ('Alive', 'Dead' or 'unknown'). */
48+
status?: Maybe<Scalars['String']>;
49+
/** The type or subspecies of the character. */
50+
type?: Maybe<Scalars['String']>;
51+
};
52+
53+
export type Characters = {
54+
__typename?: 'Characters';
55+
info?: Maybe<Info>;
56+
results?: Maybe<Array<Maybe<Character>>>;
57+
};
58+
59+
export type Episode = {
60+
__typename?: 'Episode';
61+
/** The air date of the episode. */
62+
air_date?: Maybe<Scalars['String']>;
63+
/** List of characters who have been seen in the episode. */
64+
characters: Array<Maybe<Character>>;
65+
/** Time at which the episode was created in the database. */
66+
created?: Maybe<Scalars['String']>;
67+
/** The code of the episode. */
68+
episode?: Maybe<Scalars['String']>;
69+
/** The id of the episode. */
70+
id?: Maybe<Scalars['ID']>;
71+
/** The name of the episode. */
72+
name?: Maybe<Scalars['String']>;
73+
};
74+
75+
export type Episodes = {
76+
__typename?: 'Episodes';
77+
info?: Maybe<Info>;
78+
results?: Maybe<Array<Maybe<Episode>>>;
79+
};
80+
81+
export type FilterCharacter = {
82+
gender?: InputMaybe<Scalars['String']>;
83+
name?: InputMaybe<Scalars['String']>;
84+
species?: InputMaybe<Scalars['String']>;
85+
status?: InputMaybe<Scalars['String']>;
86+
type?: InputMaybe<Scalars['String']>;
87+
};
88+
89+
export type FilterEpisode = {
90+
episode?: InputMaybe<Scalars['String']>;
91+
name?: InputMaybe<Scalars['String']>;
92+
};
93+
94+
export type FilterLocation = {
95+
dimension?: InputMaybe<Scalars['String']>;
96+
name?: InputMaybe<Scalars['String']>;
97+
type?: InputMaybe<Scalars['String']>;
98+
};
99+
100+
export type Info = {
101+
__typename?: 'Info';
102+
/** The length of the response. */
103+
count?: Maybe<Scalars['Int']>;
104+
/** Number of the next page (if it exists) */
105+
next?: Maybe<Scalars['Int']>;
106+
/** The amount of pages. */
107+
pages?: Maybe<Scalars['Int']>;
108+
/** Number of the previous page (if it exists) */
109+
prev?: Maybe<Scalars['Int']>;
110+
};
111+
112+
export type Location = {
113+
__typename?: 'Location';
114+
/** Time at which the location was created in the database. */
115+
created?: Maybe<Scalars['String']>;
116+
/** The dimension in which the location is located. */
117+
dimension?: Maybe<Scalars['String']>;
118+
/** The id of the location. */
119+
id?: Maybe<Scalars['ID']>;
120+
/** The name of the location. */
121+
name?: Maybe<Scalars['String']>;
122+
/** List of characters who have been last seen in the location. */
123+
residents: Array<Maybe<Character>>;
124+
/** The type of the location. */
125+
type?: Maybe<Scalars['String']>;
126+
};
127+
128+
export type Locations = {
129+
__typename?: 'Locations';
130+
info?: Maybe<Info>;
131+
results?: Maybe<Array<Maybe<Location>>>;
132+
};
133+
134+
export type Query = {
135+
__typename?: 'Query';
136+
/** Get a specific character by ID */
137+
character?: Maybe<Character>;
138+
/** Get the list of all characters */
139+
characters?: Maybe<Characters>;
140+
/** Get a list of characters selected by ids */
141+
charactersByIds?: Maybe<Array<Maybe<Character>>>;
142+
/** Get a specific episode by ID */
143+
episode?: Maybe<Episode>;
144+
/** Get the list of all episodes */
145+
episodes?: Maybe<Episodes>;
146+
/** Get a list of episodes selected by ids */
147+
episodesByIds?: Maybe<Array<Maybe<Episode>>>;
148+
/** Get a specific locations by ID */
149+
location?: Maybe<Location>;
150+
/** Get the list of all locations */
151+
locations?: Maybe<Locations>;
152+
/** Get a list of locations selected by ids */
153+
locationsByIds?: Maybe<Array<Maybe<Location>>>;
154+
};
155+
156+
157+
export type QueryCharacterArgs = {
158+
id: Scalars['ID'];
159+
};
160+
161+
162+
export type QueryCharactersArgs = {
163+
filter?: InputMaybe<FilterCharacter>;
164+
page?: InputMaybe<Scalars['Int']>;
165+
};
166+
167+
168+
export type QueryCharactersByIdsArgs = {
169+
ids: Array<Scalars['ID']>;
170+
};
171+
172+
173+
export type QueryEpisodeArgs = {
174+
id: Scalars['ID'];
175+
};
176+
177+
178+
export type QueryEpisodesArgs = {
179+
filter?: InputMaybe<FilterEpisode>;
180+
page?: InputMaybe<Scalars['Int']>;
181+
};
182+
183+
184+
export type QueryEpisodesByIdsArgs = {
185+
ids: Array<Scalars['ID']>;
186+
};
187+
188+
189+
export type QueryLocationArgs = {
190+
id: Scalars['ID'];
191+
};
192+
193+
194+
export type QueryLocationsArgs = {
195+
filter?: InputMaybe<FilterLocation>;
196+
page?: InputMaybe<Scalars['Int']>;
197+
};
198+
199+
200+
export type QueryLocationsByIdsArgs = {
201+
ids: Array<Scalars['ID']>;
202+
};
203+
204+
export type GetCharactersQueryVariables = Exact<{ [key: string]: never; }>;
205+
206+
207+
export type GetCharactersQuery = { __typename?: 'Query', characters?: { __typename?: 'Characters', results?: Array<{ __typename?: 'Character', id?: string | null, name?: string | null, species?: string | null, episode: Array<{ __typename?: 'Episode', id?: string | null } | null> } | null> | null } | null };
208+
209+
210+
export const GetCharactersDocument = gql`
211+
query GetCharacters {
212+
characters {
213+
results {
214+
id
215+
name
216+
species
217+
episode {
218+
id
219+
}
220+
}
221+
}
222+
}
223+
`;
224+
225+
/**
226+
* __useGetCharactersQuery__
227+
*
228+
* To run a query within a React component, call `useGetCharactersQuery` and pass it any options that fit your needs.
229+
* When your component renders, `useGetCharactersQuery` returns an object from Apollo Client that contains loading, error, and data properties
230+
* you can use to render your UI.
231+
*
232+
* @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;
233+
*
234+
* @example
235+
* const { data, loading, error } = useGetCharactersQuery({
236+
* variables: {
237+
* },
238+
* });
239+
*/
240+
export function useGetCharactersQuery(baseOptions?: Apollo.QueryHookOptions<GetCharactersQuery, GetCharactersQueryVariables>) {
241+
const options = {...defaultOptions, ...baseOptions}
242+
return Apollo.useQuery<GetCharactersQuery, GetCharactersQueryVariables>(GetCharactersDocument, options);
243+
}
244+
export function useGetCharactersLazyQuery(baseOptions?: Apollo.LazyQueryHookOptions<GetCharactersQuery, GetCharactersQueryVariables>) {
245+
const options = {...defaultOptions, ...baseOptions}
246+
return Apollo.useLazyQuery<GetCharactersQuery, GetCharactersQueryVariables>(GetCharactersDocument, options);
247+
}
248+
export type GetCharactersQueryHookResult = ReturnType<typeof useGetCharactersQuery>;
249+
export type GetCharactersLazyQueryHookResult = ReturnType<typeof useGetCharactersLazyQuery>;
250+
export type GetCharactersQueryResult = Apollo.QueryResult<GetCharactersQuery, GetCharactersQueryVariables>;

graphql.config.yml

+10
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
schema:
2+
- "https://rickandmortyapi.com/graphql"
3+
documents:
4+
- "./graphql/**/*.graphql"
5+
generates:
6+
./generated/graphql.ts:
7+
plugins:
8+
- typescript
9+
- typescript-operations
10+
- typescript-react-apollo

graphql/get-characters.query.graphql

+12
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
query GetCharacters {
2+
characters {
3+
results {
4+
id
5+
name
6+
species
7+
episode {
8+
id
9+
}
10+
}
11+
}
12+
}

lib/novu.ts

+22
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
import { Novu } from "@novu/node";
2+
3+
const novu = new Novu(process.env.NOVU_API_KEY as string);
4+
5+
type Payload = {
6+
name: string;
7+
species: string;
8+
};
9+
export const sendEmail = async (email: string, payload: Payload) => {
10+
if (!email) throw new Error("No email");
11+
12+
novu.trigger("<TRIGGER_ID>", {
13+
to: {
14+
subscriberId: email,
15+
email,
16+
},
17+
payload: {
18+
name: payload.name,
19+
species: payload.species,
20+
},
21+
});
22+
};

0 commit comments

Comments
 (0)