diff --git a/client/package.json b/client/package.json
index f0a1100..b26dd11 100644
--- a/client/package.json
+++ b/client/package.json
@@ -35,6 +35,7 @@
"react-dnd": "^11.0.0",
"react-dnd-html5-backend": "^11.0.0",
"react-dom": "^17.0.2",
+ "subscriptions-transport-ws": "^0.9.19",
"urql": "^2.0.4",
"zustand": "^3.5.5"
},
diff --git a/client/src/components/AppWrapper.jsx b/client/src/components/AppWrapper.jsx
index 631cb0b..2943f47 100644
--- a/client/src/components/AppWrapper.jsx
+++ b/client/src/components/AppWrapper.jsx
@@ -3,7 +3,7 @@ import { useAuth } from '@/hooks';
const AppWrapper = ({ children }) => {
const { isLoading } = useAuth();
- if (isLoading) return
;
+ // if (isLoading) return ;
return { children };
};
diff --git a/client/src/components/StudentList.jsx b/client/src/components/StudentList.jsx
index 7310ddb..e710bb0 100644
--- a/client/src/components/StudentList.jsx
+++ b/client/src/components/StudentList.jsx
@@ -1,11 +1,13 @@
import React, { useState } from 'react';
-import { useStudents } from '@/hooks';
+import { useStudents, useStudentList, useApplicantSubscription } from '@/hooks';
import Filters from './Filters';
import StudentCard from './StudentCard';
import styles from '../assets/styles/dashboard.module.css';
const StudentList = ({ showOnly }) => {
+ useApplicantSubscription();
+ const { applicants, isLoading: applicantsLoading } = useStudentList();
const { students, isLoading } = useStudents();
const [filtered, setFiltered] = useState([]);
diff --git a/client/src/hooks/index.js b/client/src/hooks/index.js
index f2393c5..f91770f 100644
--- a/client/src/hooks/index.js
+++ b/client/src/hooks/index.js
@@ -1,4 +1,6 @@
+export { default as useApplicantSubscription } from './useApplicantSubscription';
export { default as useAuth } from './useAuth';
export { default as useStudents } from './useStudents';
export { default as useSuggestions } from './useSuggestions';
+export { default as useStudentList } from './useStudentList';
export { default as useRequireAuth } from './useRequireAuth';
diff --git a/client/src/hooks/useApplicantSubscription.js b/client/src/hooks/useApplicantSubscription.js
new file mode 100644
index 0000000..0f178d8
--- /dev/null
+++ b/client/src/hooks/useApplicantSubscription.js
@@ -0,0 +1,9 @@
+import { useSubscription } from 'urql';
+import { subscriptions } from 'common';
+
+export default function useApplicantSubscription() {
+ const [{ result, fetching }] = useSubscription({ query: subscriptions.APPLICANTS_CHANGED });
+
+ console.log(result);
+ return null;
+}
diff --git a/client/src/hooks/useAuth.js b/client/src/hooks/useAuth.js
index 5cd6a31..e36f0c3 100644
--- a/client/src/hooks/useAuth.js
+++ b/client/src/hooks/useAuth.js
@@ -16,7 +16,7 @@ const useStore = create((set) => ({
}));
export default function useAuth() {
- const [{ data, fetching }] = useQuery({ query: queries.me });
+ const [{ data, fetching }] = useQuery({ query: queries.ME });
const { user, isLoggingIn, setUser, finishLoading, login, isLoading } = useStore();
const router = useRouter();
diff --git a/client/src/hooks/useStudentList.js b/client/src/hooks/useStudentList.js
new file mode 100644
index 0000000..f614e92
--- /dev/null
+++ b/client/src/hooks/useStudentList.js
@@ -0,0 +1,8 @@
+import { useQuery } from 'urql';
+import { queries } from 'common';
+
+export default function useStudents() {
+ const [{ data, fetching }] = useQuery({ query: queries.APPLICANT_LIST });
+
+ return { isLoading: fetching, applicants: data ? data.applicants : null };
+}
diff --git a/client/src/pages/index.jsx b/client/src/pages/index.jsx
index d9e9d2d..f5142b0 100644
--- a/client/src/pages/index.jsx
+++ b/client/src/pages/index.jsx
@@ -1,11 +1,13 @@
import { useRequireAuth } from '@/hooks';
import StudentDetail from '@/components/StudentDetail';
import SidebarLayout from '@/components/SidebarLayout';
+import { useQuery, useSubscription } from 'urql';
+import { subscriptions, queries } from 'common';
function Index() {
- const user = useRequireAuth();
+ // const user = useRequireAuth();
- if (!user) return ;
+ // if (!user) return ;
return ;
}
diff --git a/client/src/pages/login.jsx b/client/src/pages/login.jsx
index c692fa5..4b1c291 100644
--- a/client/src/pages/login.jsx
+++ b/client/src/pages/login.jsx
@@ -2,15 +2,11 @@ import { useEffect } from 'react';
import { useRouter } from 'next/router';
import { useAuth } from '@/hooks';
import { API_URL } from '@/constants';
-import { useQuery } from 'urql';
-import { queries } from 'common';
import SocialButton from '../components/SocialButton';
import styles from '../assets/styles/pending.module.css';
export default function Login() {
- const router = useRouter();
- const [result] = useQuery({ query: queries.me });
// const { user } = useAuth();
/*
diff --git a/client/src/urql-client.js b/client/src/urql-client.js
index 512f4c9..0c8a4d5 100644
--- a/client/src/urql-client.js
+++ b/client/src/urql-client.js
@@ -1,15 +1,37 @@
import { API_URL } from '@/constants';
import { cacheExchange } from '@urql/exchange-graphcache';
-import { createClient, ssrExchange, dedupExchange, fetchExchange } from 'urql';
+import {
+ createClient,
+ ssrExchange,
+ dedupExchange,
+ fetchExchange,
+ subscriptionExchange
+} from 'urql';
+import { SubscriptionClient } from 'subscriptions-transport-ws';
// Use a normalized cache
const cache = cacheExchange({});
const isServerSide = typeof window === 'undefined';
const ssrCache = ssrExchange({ isClient: !isServerSide });
+
+const subscriptionClient = !isServerSide
+ ? new SubscriptionClient(`ws${API_URL.replace(/^http?/, '')}/graphql`, {
+ reconnect: true
+ })
+ : null;
+
const client = createClient({
url: `${API_URL}/graphql`,
- exchanges: [dedupExchange, cache, fetchExchange, ssrCache]
+ exchanges: [
+ dedupExchange,
+ cache,
+ fetchExchange,
+ ssrCache,
+ subscriptionExchange({
+ forwardSubscription: (operation) => subscriptionClient.request(operation)
+ })
+ ]
});
export { client, ssrCache };
diff --git a/common/src/graphql/queries/applicantList.ts b/common/src/graphql/queries/applicantList.ts
new file mode 100644
index 0000000..e9d18ac
--- /dev/null
+++ b/common/src/graphql/queries/applicantList.ts
@@ -0,0 +1,15 @@
+import gql from 'graphql-tag';
+
+export default gql`
+ query ApplicantList {
+ applicants {
+ id
+ isAlumni
+ firstname
+ lastname
+ suggestions {
+ id
+ }
+ }
+ }
+`;
diff --git a/common/src/graphql/queries/index.ts b/common/src/graphql/queries/index.ts
index 6eaa73b..e7c6cfb 100644
--- a/common/src/graphql/queries/index.ts
+++ b/common/src/graphql/queries/index.ts
@@ -1 +1,2 @@
-export { default as me } from './me';
+export { default as ME } from './me';
+export { default as APPLICANT_LIST } from './applicantList';
diff --git a/common/src/graphql/subscriptions/applicantsChanged.ts b/common/src/graphql/subscriptions/applicantsChanged.ts
new file mode 100644
index 0000000..a60d65d
--- /dev/null
+++ b/common/src/graphql/subscriptions/applicantsChanged.ts
@@ -0,0 +1,18 @@
+import gql from 'graphql-tag';
+
+export default gql`
+ subscription ApplicantsSub {
+ applicantsChanged {
+ id
+ suggestions {
+ id
+ }
+ projects {
+ id
+ }
+ firstname
+ lastname
+ isAlumni
+ }
+ }
+`;
diff --git a/common/src/graphql/subscriptions/index.ts b/common/src/graphql/subscriptions/index.ts
new file mode 100644
index 0000000..2ca0826
--- /dev/null
+++ b/common/src/graphql/subscriptions/index.ts
@@ -0,0 +1 @@
+export { default as APPLICANTS_CHANGED } from './applicantsChanged';
diff --git a/common/src/index.ts b/common/src/index.ts
index 07da892..272e0e0 100644
--- a/common/src/index.ts
+++ b/common/src/index.ts
@@ -6,3 +6,4 @@ export * from './types/Project';
export * from './types/Profile';
export * from './types/Skill';
export * as queries from './graphql/queries';
+export * as subscriptions from './graphql/subscriptions';
diff --git a/package-lock.json b/package-lock.json
index 16ed74f..c1b8036 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -36,6 +36,7 @@
"react-dnd": "^11.0.0",
"react-dnd-html5-backend": "^11.0.0",
"react-dom": "^17.0.2",
+ "subscriptions-transport-ws": "^0.9.19",
"urql": "^2.0.4",
"zustand": "^3.5.5"
},
@@ -9687,6 +9688,12 @@
"url": "https://github.com/sponsors/jaydenseric"
}
},
+ "node_modules/faker": {
+ "version": "5.5.3",
+ "resolved": "https://registry.npmjs.org/faker/-/faker-5.5.3.tgz",
+ "integrity": "sha512-wLTv2a28wjUyWkbnX7u/ABZBkUkIF2fCd73V6P2oFqEGEktDfzWx4UxrSqtPRw0xPRAcjeAOIiJWqZm3pP4u3g==",
+ "dev": true
+ },
"node_modules/fast-deep-equal": {
"version": "3.1.3",
"resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz",
@@ -18777,9 +18784,9 @@
}
},
"node_modules/ws": {
- "version": "7.5.1",
- "resolved": "https://registry.npmjs.org/ws/-/ws-7.5.1.tgz",
- "integrity": "sha512-2c6faOUH/nhoQN6abwMloF7Iyl0ZS2E9HGtsiLrWn0zOOMWlhtDmdf/uihDt6jnuCxgtwGBNy6Onsoy2s2O2Ow==",
+ "version": "7.5.3",
+ "resolved": "https://registry.npmjs.org/ws/-/ws-7.5.3.tgz",
+ "integrity": "sha512-kQ/dHIzuLrS6Je9+uv81ueZomEwH0qVYstcAQ4/Z93K8zeko9gtAbttJWzoC5ukqXY1PpoouV3+VSOqEAFt5wg==",
"engines": {
"node": ">=8.3.0"
},
@@ -19071,6 +19078,7 @@
"eslint": "^7.22.0",
"eslint-config-prettier": "^8.1.0",
"eslint-plugin-prettier": "^3.3.1",
+ "faker": "^5.5.3",
"jest": "^26.6.3",
"prettier": "^2.2.1",
"prisma": "^2.26.0",
@@ -24038,6 +24046,7 @@
"react-dnd": "^11.0.0",
"react-dnd-html5-backend": "^11.0.0",
"react-dom": "^17.0.2",
+ "subscriptions-transport-ws": "^0.9.19",
"typescript": "^4.3.5",
"urql": "^2.0.4",
"zustand": "^3.5.5"
@@ -26702,6 +26711,12 @@
"resolved": "https://registry.npmjs.org/extract-files/-/extract-files-9.0.0.tgz",
"integrity": "sha512-CvdFfHkC95B4bBBk36hcEmvdR2awOdhhVUYH6S/zrVj3477zven/fJMYg7121h4T1xHZC+tetUpubpAhxwI7hQ=="
},
+ "faker": {
+ "version": "5.5.3",
+ "resolved": "https://registry.npmjs.org/faker/-/faker-5.5.3.tgz",
+ "integrity": "sha512-wLTv2a28wjUyWkbnX7u/ABZBkUkIF2fCd73V6P2oFqEGEktDfzWx4UxrSqtPRw0xPRAcjeAOIiJWqZm3pP4u3g==",
+ "dev": true
+ },
"fast-deep-equal": {
"version": "3.1.3",
"resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz",
@@ -31669,10 +31684,11 @@
"@typescript-eslint/eslint-plugin": "^4.19.0",
"@typescript-eslint/parser": "^4.19.0",
"apollo-server-express": "^2.25.2",
- "common": "*",
+ "common": "^0.0.1",
"eslint": "^7.22.0",
"eslint-config-prettier": "^8.1.0",
"eslint-plugin-prettier": "^3.3.1",
+ "faker": "^5.5.3",
"graphql": "^15.5.1",
"graphql-tools": "^7.0.5",
"graphql-type-json": "^0.3.2",
@@ -33800,9 +33816,9 @@
}
},
"ws": {
- "version": "7.5.1",
- "resolved": "https://registry.npmjs.org/ws/-/ws-7.5.1.tgz",
- "integrity": "sha512-2c6faOUH/nhoQN6abwMloF7Iyl0ZS2E9HGtsiLrWn0zOOMWlhtDmdf/uihDt6jnuCxgtwGBNy6Onsoy2s2O2Ow==",
+ "version": "7.5.3",
+ "resolved": "https://registry.npmjs.org/ws/-/ws-7.5.3.tgz",
+ "integrity": "sha512-kQ/dHIzuLrS6Je9+uv81ueZomEwH0qVYstcAQ4/Z93K8zeko9gtAbttJWzoC5ukqXY1PpoouV3+VSOqEAFt5wg==",
"requires": {}
},
"xml-name-validator": {
diff --git a/server/prisma/seed.ts b/server/prisma/seed.ts
index 8dc5fc2..9a9a585 100644
--- a/server/prisma/seed.ts
+++ b/server/prisma/seed.ts
@@ -7,7 +7,6 @@ async function main() {
console.log('seeding...');
for (let i = 0; i < 10; i++) {
-
const genders = ['male', 'female', 'nonbinary'];
await prisma.applicant.upsert({
diff --git a/server/src/schema.gql b/server/src/schema.gql
index 1a52a7f..d622128 100644
--- a/server/src/schema.gql
+++ b/server/src/schema.gql
@@ -2,12 +2,6 @@
# THIS FILE WAS AUTOMATICALLY GENERATED (DO NOT MODIFY)
# ------------------------------------------------------
-type Skillset {
- id: Int!
- name: String!
- level: String!
-}
-
type Address {
id: Int!
addressLine: String!
@@ -17,6 +11,13 @@ type Address {
country: String!
}
+type Profile {
+ id: Float!
+ name: String!
+ image_url: String
+ applicants: [Applicant!]
+}
+
type User {
id: Int!
uuid: String!
@@ -39,11 +40,6 @@ A date-time string at UTC, such as 2019-12-03T09:54:33Z, compliant with the date
"""
scalar DateTime
-type Skill {
- id: Int!
- name: String!
-}
-
type Project {
id: Int!
uuid: String!
@@ -54,20 +50,10 @@ type Project {
leadCoach: User
coaches: [User!]
applicants: [Applicant!]
- profiles: [Profile!]
- skills: [Skill!]
createdAt: DateTime!
updatedAt: DateTime!
}
-type Profile {
- id: Float!
- name: String!
- image_url: String
- applicants: [Applicant!]
- projects: [Project!]
-}
-
type Suggestion {
id: Int!
status: String!
@@ -94,14 +80,11 @@ type Applicant {
suggestions: [Suggestion!]
projects: [Project!]
profiles: [Profile!]
- skillset: [Skillset!]
}
type Query {
- applicants(where: FilterApplicantInput): [Applicant!]!
+ applicants: [Applicant!]!
applicant(uuid: String!): Applicant!
- skills: [Skill!]!
- skill(id: Float!): Skill!
users: [User!]!
user(uuid: String!): User!
suggestions: [Suggestion!]!
@@ -113,86 +96,12 @@ type Query {
logout: User
}
-input FilterApplicantInput {
- AND: [FilterApplicantInput!]
- OR: [FilterApplicantInput!]
- NOT: [FilterApplicantInput!]
- email: StringFilter
- firstname: StringFilter
- lastname: StringFilter
- callname: StringFilter
- gender: StringFilter
- phone: StringFilter
- nationality: StringFilter
- address: FilterAddressInput
- isAlumni: Boolean
- profiles_every: FilterProfileInput
- projects_every: FilterProjectInput
- suggestions_every: FilterSuggestionInput
- skills_every: FilterSkillInput
-}
-
-input StringFilter {
- equals: String
- in: [String!]
- notIn: [String!]
- lt: String
- lte: String
- gt: String
- gte: String
- contains: String
- startsWith: String
- endsWith: String
-}
-
-input FilterAddressInput {
- AND: FilterAddressInput
- OR: FilterAddressInput
- NOT: FilterAddressInput
- addressLine: StringFilter
- postalCode: StringFilter
- city: StringFilter
- state: StringFilter
- country: StringFilter
-}
-
-input FilterProfileInput {
- AND: [FilterProfileInput!]
- OR: [FilterProfileInput!]
- NOT: [FilterProfileInput!]
- name: StringFilter
-}
-
-input FilterProjectInput {
- AND: FilterProjectInput
- OR: FilterProjectInput
- NOT: FilterProjectInput
- name: StringFilter
- client: StringFilter
- leadCoachId: Float
-}
-
-input FilterSuggestionInput {
- AND: FilterSuggestionInput
- OR: FilterSuggestionInput
- NOT: FilterSuggestionInput
- status: StringFilter
-}
-
-input FilterSkillInput {
- AND: FilterSkillInput
- OR: FilterSkillInput
- NOT: FilterSkillInput
- name: StringFilter
-}
-
type Mutation {
createApplicant(input: CreateApplicantInput!): Applicant!
updateApplicant(input: UpdateApplicantInput!, uuid: String!): Applicant!
deleteApplicant(uuid: String!): Boolean!
addApplicantToProject(projectId: Int!, applicantId: Int!): Boolean!
removeApplicantFromProject(projectId: Int!, applicantId: Int!): Boolean!
- addSkillToApplicant(level: String!, skill: String!, applicantId: Int!): Boolean!
updateUser(input: UpdateUserInput!, uuid: String!): User!
deleteUser(uuid: String!): Boolean!
addUserToProject(projectId: Int!, userId: Int!): Boolean!
@@ -203,14 +112,11 @@ type Mutation {
createProject(input: CreateProjectInput!): Project!
updateProject(input: UpdateProjectInput!, uuid: String!): Project!
deleteProject(uuid: String!): Boolean!
- addSkillToProject(skill: String!, projectId: Int!): Boolean!
createProfile(input: CreateProfileInput!): Profile!
updateProfile(input: UpdateProfileInput!, id: Float!): Profile!
deleteProfile(id: Float!): Boolean!
- addProfileToApplicant(profileId: Int!, applicantId: Int!): Boolean!
- removeProfileToApplicant(profileId: Int!, applicantId: Int!): Boolean!
- addProfileToProject(profileId: Int!, projectId: Int!): Boolean!
- removeProfileToProject(profileId: Int!, projectId: Int!): Boolean!
+ addProfileToApplicant(projectId: Int!, applicantId: Int!): Boolean!
+ removeProfileToApplicant(projectId: Int!, applicantId: Int!): Boolean!
}
input CreateApplicantInput {