From 18239a214e8860ea192d2ebf03a42fba6b2574aa Mon Sep 17 00:00:00 2001 From: Edward Knowles Date: Sun, 5 Jul 2020 18:09:48 +0100 Subject: [PATCH] feat: add membership to dashboard - link to profile on dashboard #3 --- .eslintrc.js | 1 + .vercelignore | 1 + app/components/community-cards/index.tsx | 5 +- app/components/header/header.component.tsx | 1 - app/components/user-community-list/index.ts | 3 + .../user-community-list.component.tsx | 77 ++++++++++ assets/antd.less | 136 +++++++++--------- ...ate-or-update-community-account-profile.js | 2 + fauna/functions/join-community.js | 2 + fauna/roles/public_role.js | 13 ++ fauna/roles/user_account_role.js | 2 - fauna/schema.gql | 1 + pages/beta/join.tsx | 38 ----- pages/beta/test.tsx | 91 ------------ pages/communities/[communityId]/index.tsx | 2 +- pages/communities/[communityId]/join.tsx | 48 ++++++- .../communities/[communityId]/membership.tsx | 4 +- pages/dashboard.tsx | 43 ++++++ pages/dashboard/index.tsx | 31 ---- pages/users/[userProfileId].tsx | 57 ++------ 20 files changed, 274 insertions(+), 284 deletions(-) create mode 100644 app/components/user-community-list/index.ts create mode 100644 app/components/user-community-list/user-community-list.component.tsx delete mode 100644 pages/beta/join.tsx delete mode 100644 pages/beta/test.tsx create mode 100644 pages/dashboard.tsx delete mode 100644 pages/dashboard/index.tsx diff --git a/.eslintrc.js b/.eslintrc.js index 204b429..b2c86c9 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -26,6 +26,7 @@ module.exports = { 'prettier/react', ], rules: { + 'no-underscore-dangle': 0, 'react/jsx-filename-extension': [1, { extensions: ['.ts', '.tsx'] }], 'import/extensions': 0, 'react/prop-types': 0, diff --git a/.vercelignore b/.vercelignore index a680367..af992db 100644 --- a/.vercelignore +++ b/.vercelignore @@ -1 +1,2 @@ .next +.vercel diff --git a/app/components/community-cards/index.tsx b/app/components/community-cards/index.tsx index d34b420..8878fa5 100644 --- a/app/components/community-cards/index.tsx +++ b/app/components/community-cards/index.tsx @@ -9,10 +9,13 @@ const { Meta } = Card; const tagsData = ['Recruiting', 'English', 'German', 'French']; +const DEFAULT_COVER_IMG = + 'https://images.unsplash.com/photo-1480506132288-68f7705954bd?ixlib=rb-1.2.1&ixid=eyJhcHBfaWQiOjEyMDd9&auto=format&fit=crop&w=3386&q=80'; + export const CommunityCard: React.FC = ({ id, name, - imageUrl, + imageUrl = DEFAULT_COVER_IMG, avatarUrl, language, }) => ( diff --git a/app/components/header/header.component.tsx b/app/components/header/header.component.tsx index ed76b5b..ba644dc 100644 --- a/app/components/header/header.component.tsx +++ b/app/components/header/header.component.tsx @@ -1,5 +1,4 @@ import { Alert, Button, Space, Layout, Menu } from 'antd'; -import { mistyrose } from 'color-name'; import Link from 'next/link'; import React, { useContext } from 'react'; import { useRouter } from 'next/router'; diff --git a/app/components/user-community-list/index.ts b/app/components/user-community-list/index.ts new file mode 100644 index 0000000..cf7292f --- /dev/null +++ b/app/components/user-community-list/index.ts @@ -0,0 +1,3 @@ +import UserCommunityList from './user-community-list.component'; + +export default UserCommunityList; diff --git a/app/components/user-community-list/user-community-list.component.tsx b/app/components/user-community-list/user-community-list.component.tsx new file mode 100644 index 0000000..9463bb4 --- /dev/null +++ b/app/components/user-community-list/user-community-list.component.tsx @@ -0,0 +1,77 @@ +import Link from 'next/link'; +import React from 'react'; +import { List, Avatar } from 'antd'; +import { request } from 'graphql-request'; +import useSWR from 'swr'; + +const useDashboardData = (profileId) => { + const { data, error } = useSWR(profileId, (id) => + request( + '/api/graphql', + `query ViewProfile($id: ID!) { + profile: findUserProfileByID(id: $id) { + memberships { + data { + role + createdAt + communityProfile { + _id + name + iconUrl + } + } + } + } + }`, + { id } + ) + ); + return { data, error }; +}; + +const UserCommunityList = ({ userId }) => { + const { data } = useDashboardData(userId); + + const items = data ? (data.profile.memberships?.data as any[]) : []; + + // todo set locale to be based on the users localeCode + const formatter = new Intl.DateTimeFormat('en-GB'); + + return ( + ( + Since {formatter.format(new Date(createdAt))}} + actions={[ + + View + , + ]} + > + } + title={ + + {communityProfile.name} + + } + description={role} + /> + + )} + /> + ); +}; + +export default UserCommunityList; diff --git a/assets/antd.less b/assets/antd.less index c22976c..e3ecccd 100644 --- a/assets/antd.less +++ b/assets/antd.less @@ -5,81 +5,85 @@ @primary-color: #1eaa0d; @body-background: #06242b; @component-background: #052027; -//@layout-body-background: #172d36; -@layout-body-background: @component-background; +@layout-body-background: @body-background; @layout-header-background: @component-background; @layout-sider-background: @component-background; @layout-trigger-background: @component-background; @menu-dark-bg: @layout-header-background; + +@border-color-base: #093945; // base border outline a component +@border-color-split: #072c36; // split border inside a component + //@primary-color: #4059ff; -//@page-header-back-color: @text-color-secondary; -//@select-item-selected-bg: @skeleton-color; -//@info-color: #579ed9; -//@warning-color: #d46b07; -//@error-color: #f12222; -//@highlight-color: #494848; -//@icon-color-hover: #fadb14; -//@heading-color: fade(@white, 85%); +@page-header-back-color: @text-color-secondary; +@select-item-selected-bg: @skeleton-color; +@info-color: #579ed9; +@warning-color: #d46b07; +@error-color: #f12222; +@highlight-color: #494848; +@icon-color-hover: #fadb14; +@heading-color: fade(@white, 85%); //@text-color: #ffffff; //@text-color-secondary: #3b817f; //@text-color-inverse: #ffffff; //@heading-color-dark: #f7f7f7; //@text-color-dark: #fdfdfd; //@text-color-secondary-dark: #f1f1f1; -//@text-selection-bg: #fa8c16; -//@border-color-base: #0c5669; -//@border-color-split: #073742; -//@border-style-base: solid; -//@font-feature-settings-base: tnum; -//@disabled-color: fade(@white, 30%); -//@disabled-color-dark: fade(@white, 30%); -//@outline-width: 4px; -//@background-color-light: fade(@white, 4%); -//@background-color-base: fade(@white, 8%); -//@item-active-bg: black; -//@item-hover-bg: fade(@white, 5%); -//@shadow-color: #080808; -//@shadow-1-up: 0 -6px 16px -8px rgba(0, 0, 0, 0.24), 0 -9px 28px 0 rgba(0, 0, 0, 0.15),0 -12px 48px 16px rgba(0, 0, 0, 0.09); -//@shadow-1-down: 0 6px 16px -8px rgba(0, 0, 0, 0.24), 0 9px 28px 0 rgba(0, 0, 0, 0.15), 0 12px 48px 16px rgba(0, 0, 0, 0.09); -//@shadow-1-left: -6px 0 16px -8px rgba(0, 0, 0, 0.08), -9px 0 28px 0 rgba(0, 0, 0, 0.05), -12px 0 48px 16px rgba(0, 0, 0, 0.03); -//@shadow-1-right: 6px 0 16px -8px rgba(0, 0, 0, 0.24), 9px 0 28px 0 rgba(0, 0, 0, 0.15), 12px 0 48px 16px rgba(0, 0, 0, 0.09); -//@shadow-2: 0 3px 6px -4px rgba(0, 0, 0, 0.36), 0 6px 16px 0 rgba(0, 0, 0, 0.24), 0 9px 28px 8px rgba(0, 0, 0, 0.15); +@text-selection-bg: #fa8c16; +@border-color-base: #0c5669; +@border-color-split: #073742; +@border-style-base: solid; +@font-feature-settings-base: tnum; +@disabled-color: fade(@white, 30%); +@disabled-color-dark: fade(@white, 30%); +@outline-width: 4px; +@background-color-light: fade(@white, 4%); +@background-color-base: fade(@white, 8%); +@item-active-bg: black; +@item-hover-bg: fade(@white, 5%); +@shadow-color: #080808; +@shadow-1-up: 0 -6px 16px -8px rgba(0, 0, 0, 0.24), 0 -9px 28px 0 rgba(0, 0, 0, 0.15),0 -12px 48px 16px rgba(0, 0, 0, 0.09); +@shadow-1-down: 0 6px 16px -8px rgba(0, 0, 0, 0.24), 0 9px 28px 0 rgba(0, 0, 0, 0.15), 0 12px 48px 16px rgba(0, 0, 0, 0.09); +@shadow-1-left: -6px 0 16px -8px rgba(0, 0, 0, 0.08), -9px 0 28px 0 rgba(0, 0, 0, 0.05), -12px 0 48px 16px rgba(0, 0, 0, 0.03); +@shadow-1-right: 6px 0 16px -8px rgba(0, 0, 0, 0.24), 9px 0 28px 0 rgba(0, 0, 0, 0.15), 12px 0 48px 16px rgba(0, 0, 0, 0.09); +@shadow-2: 0 3px 6px -4px rgba(0, 0, 0, 0.36), 0 6px 16px 0 rgba(0, 0, 0, 0.24), 0 9px 28px 8px rgba(0, 0, 0, 0.15); //@btn-font-weight: 600; -//@btn-primary-color: #000203; -//@btn-default-bg: transparent; -//@checkbox-check-color: #1b1212; -//@input-placeholder-color: fade(@white, 30%); -//@input-bg: transparent; -//@input-number-handler-active-bg: @popover-background; -//@select-item-selected-font-weight: 600; -//@tooltip-bg: #434343; -//@popover-bg: @popover-background; -//@modal-header-bg: @popover-background; -//@menu-popup-bg: @popover-background; -//@menu-dark-submenu-bg: @black; -//@table-header-bg: #1d1d1d; -//@table-header-sort-bg: #262626; -//@table-body-sort-bg: #1d1a1a; -//@table-row-hover-bg: #262626; -//@table-selected-row-bg: #0f0e0e; -//@table-expanded-row-bg: #131212; -//@table-padding-vertical: 8px; -//@badge-text-color: @white; -//@card-actions-background: fade(@white, 4%); -//@card-shadow: 0 1px 2px -2px rgba(0, 0, 0, 0.48), 0 3px 6px 0 rgba(0, 0, 0, 0.36), 0 5px 12px 4px rgba(0, 0, 0, 0.27); -//@avatar-bg: #5a5a5a; -//@pagination-item-bg-active: transparent; -//@slider-rail-background-color: fade(@white, 20%); -//@slider-rail-background-color-hover: @slider-rail-background-color; -//@slider-track-background-color: @primary-color; -//@slider-handle-color: @primary-color; -//@skeleton-color: #303030; -// -//@alert-success-border-color: @green-3; -//@alert-success-bg-color: @component-background; // fade(@green-1, 10%); -//@alert-info-border-color: @blue-3; -//@alert-info-bg-color: @component-background; // fade(@blue-1, 10%); -//@alert-warning-border-color: @gold-3; -//@alert-warning-bg-color: @component-background; // fade(@gold-1, 10%); -//@alert-error-border-color: @red-3; -//@alert-error-bg-color: @component-background; // fade(@red-1, 10%); +@btn-primary-color: #000203; +@btn-default-bg: transparent; +@checkbox-check-color: #1b1212; +@input-placeholder-color: fade(@white, 30%); +@input-bg: transparent; +@input-number-handler-active-bg: @popover-background; +@select-item-selected-font-weight: 600; +@tooltip-bg: #434343; +@popover-bg: @popover-background; +@modal-header-bg: @popover-background; +@menu-popup-bg: @popover-background; +@menu-dark-submenu-bg: @black; +@table-header-bg: #1d1d1d; +@table-header-sort-bg: #262626; +@table-body-sort-bg: #1d1a1a; +@table-row-hover-bg: #262626; +@table-selected-row-bg: #0f0e0e; +@table-expanded-row-bg: #131212; +@table-padding-vertical: 8px; +@badge-text-color: @white; +@card-actions-background: fade(@white, 4%); +@card-shadow: 0 1px 2px -2px rgba(0, 0, 0, 0.48), 0 3px 6px 0 rgba(0, 0, 0, 0.36), 0 5px 12px 4px rgba(0, 0, 0, 0.27); +@avatar-bg: #5a5a5a; +@pagination-item-bg-active: transparent; +@slider-rail-background-color: fade(@white, 20%); +@slider-rail-background-color-hover: @slider-rail-background-color; +@slider-track-background-color: @primary-color; +@slider-handle-color: @primary-color; +@skeleton-color: #072a32; +@skeleton-to-color: @component-background; + +@alert-success-border-color: @green-3; +@alert-success-bg-color: @component-background; // fade(@green-1, 10%); +@alert-info-border-color: @blue-3; +@alert-info-bg-color: @component-background; // fade(@blue-1, 10%); +@alert-warning-border-color: @gold-3; +@alert-warning-bg-color: @component-background; // fade(@gold-1, 10%); +@alert-error-border-color: @red-3; +@alert-error-bg-color: @component-background; // fade(@red-1, 10%); diff --git a/fauna/functions/create-or-update-community-account-profile.js b/fauna/functions/create-or-update-community-account-profile.js index 26edd28..ad2326e 100644 --- a/fauna/functions/create-or-update-community-account-profile.js +++ b/fauna/functions/create-or-update-community-account-profile.js @@ -14,6 +14,7 @@ const { Var, Update, Create, + Now, Collection, } = query; @@ -125,6 +126,7 @@ module.exports = { Var('createdCommunityProfile') ), role: 'OWNER', + createdAt: Now(), }, }), }, diff --git a/fauna/functions/join-community.js b/fauna/functions/join-community.js index 06d8877..0eb1544 100644 --- a/fauna/functions/join-community.js +++ b/fauna/functions/join-community.js @@ -5,6 +5,7 @@ const { Query, Lambda, Let, + Now, Ref, Match, Index, @@ -41,6 +42,7 @@ module.exports = { Get(Var('membership')), Create(Collection('memberships'), { data: { + createdAt: Now(), role: 'ROOKIE', userProfile: Ref( Collection('user_profiles'), diff --git a/fauna/roles/public_role.js b/fauna/roles/public_role.js index 6f24361..e433233 100644 --- a/fauna/roles/public_role.js +++ b/fauna/roles/public_role.js @@ -5,7 +5,20 @@ const { Collection, Index, Function } = query; module.exports = { name: 'public_role', + membership: [{ resource: Collection('user_accounts') }], privileges: [ + { + resource: Collection('memberships'), + actions: { + read: true, + }, + }, + { + resource: Index('user_profile_memberships_by_userProfile'), + actions: { + unrestricted_read: true, + }, + }, { resource: Collection('user_profiles'), actions: { diff --git a/fauna/roles/user_account_role.js b/fauna/roles/user_account_role.js index 4d67d7d..4c6cc76 100644 --- a/fauna/roles/user_account_role.js +++ b/fauna/roles/user_account_role.js @@ -2,7 +2,6 @@ const { query } = require('faunadb'); const OnlyUpdateProfileIfOwner = require('../predicates/only-update-profile-if-owner'); const OnlyReadOwnUserAccount = require('../predicates/only-read-own-user-account'); -const publicRole = require('./public_role'); const { Collection, Function } = query; @@ -10,7 +9,6 @@ module.exports = { name: 'user_account_role', membership: [{ resource: Collection('user_accounts') }], privileges: [ - ...publicRole.privileges, { resource: Collection('user_profiles'), actions: { diff --git a/fauna/schema.gql b/fauna/schema.gql index bb91a38..794f7f9 100644 --- a/fauna/schema.gql +++ b/fauna/schema.gql @@ -58,6 +58,7 @@ type Membership @collection(name: "memberships") { userProfile: UserProfile! @relation(name: "user_profile_memberships") communityProfile: CommunityProfile! @relation(name: "community_profile_membership") role: MembershipRole! # owner, board, director, leader, member, rookie + createdAt: Time! } type UserProfile @collection(name: "user_profiles") { diff --git a/pages/beta/join.tsx b/pages/beta/join.tsx deleted file mode 100644 index 64dc876..0000000 --- a/pages/beta/join.tsx +++ /dev/null @@ -1,38 +0,0 @@ -import { Button, Space, Steps, Card } from 'antd'; -import React from 'react'; -import Head from 'next/head'; - -import Footer from 'app/components/footer'; - -const { Step } = Steps; - -const JoinPage: React.FC = () => ( -
- - Join | 300.team - -
- - - 1}> - - - - - - - - -
- -
-
-); - -export default JoinPage; diff --git a/pages/beta/test.tsx b/pages/beta/test.tsx deleted file mode 100644 index d85714f..0000000 --- a/pages/beta/test.tsx +++ /dev/null @@ -1,91 +0,0 @@ -import { Space, Steps, Typography, Alert, Card, Avatar } from 'antd'; -import React from 'react'; -import Head from 'next/head'; -import { UserOutlined } from '@ant-design/icons'; - -import CommunityCards from 'app/components/community-cards'; - -const { Title } = Typography; -const { Step } = Steps; - -const TestPage: React.FC = () => ( - <> - - 300.team - - - -
- 1}> - - - - - -
-
- -
- Communities Made Simple - a subscription platform for growing communities - subscription manager for your club - - grow your community with managed subscriptions and automated - on-boarding - -
- -
- - - - -
- -
-
- } /> -
-
- - - - - -
-
- -
-
- Featured Communities -
- -
-
- -); - -export default TestPage; diff --git a/pages/communities/[communityId]/index.tsx b/pages/communities/[communityId]/index.tsx index 740a39c..a5dc66b 100644 --- a/pages/communities/[communityId]/index.tsx +++ b/pages/communities/[communityId]/index.tsx @@ -115,7 +115,7 @@ const CommunityPage: React.FC - TODO + TODO COMMUNITY PROFILE ); diff --git a/pages/communities/[communityId]/join.tsx b/pages/communities/[communityId]/join.tsx index 88d4023..c2561f2 100644 --- a/pages/communities/[communityId]/join.tsx +++ b/pages/communities/[communityId]/join.tsx @@ -1,11 +1,35 @@ +import { useRouter } from 'next/router'; import React from 'react'; import Head from 'next/head'; -import { Row, Col, Typography, Space, Avatar, Checkbox } from 'antd'; +import { Row, Col, Typography, Space, Avatar, Checkbox, Skeleton } from 'antd'; import SubscriptionPlanColumn from 'app/components/subscription-plan-column'; +import useSWR from 'swr'; +import { request } from 'graphql-request'; const { Title, Text, Paragraph } = Typography; -const ManageMembership: React.FC = () => { +const useCommunityData = (communityId) => { + const { data, error } = useSWR(communityId, (id) => + request( + '/api/graphql', + `query JoinCommunity($id: ID!) { + community: findCommunityProfileByID(id: $id) { + name + iconUrl + } + }`, + { id } + ) + ); + return { data, error }; +}; + +const JoinCommunity: React.FC = () => { + const router = useRouter(); + const { communityId } = router.query; + const { error, data } = useCommunityData(communityId); + // get data + return ( <> @@ -13,8 +37,22 @@ const ManageMembership: React.FC = () => {
- - data.name + {data + ? [ + , + + {data.community.name} + , + ] + : [ + , + , + ]} @@ -84,4 +122,4 @@ const ManageMembership: React.FC = () => { ); }; -export default ManageMembership; +export default JoinCommunity; diff --git a/pages/communities/[communityId]/membership.tsx b/pages/communities/[communityId]/membership.tsx index cce0cf2..384515e 100644 --- a/pages/communities/[communityId]/membership.tsx +++ b/pages/communities/[communityId]/membership.tsx @@ -67,8 +67,8 @@ const ManageMembership: React.FC = () => { onBack={() => router.push(`/communities/${communityId}`)} breadcrumb={{ routes, itemRender }} /> - TODO - {JSON.stringify(roleData)} +
TODO MANAGE MEMBERSHIP
+
{JSON.stringify(roleData)}
); diff --git a/pages/dashboard.tsx b/pages/dashboard.tsx new file mode 100644 index 0000000..55cd9b1 --- /dev/null +++ b/pages/dashboard.tsx @@ -0,0 +1,43 @@ +import UserCommunityList from 'app/components/user-community-list'; +import React, { useContext } from 'react'; +import Head from 'next/head'; +import { Button, PageHeader } from 'antd'; +import { UserContext } from 'app/contexts/user.context'; +import { useRouter } from 'next/router'; + +const DashboardPage: React.FC = () => { + const router = useRouter(); + const { logout, user } = useContext(UserContext); + + const content = user && ( +
+ router.push(`/users/${user._id}`)} + > + My Profile + , + , + ]} + /> + +
+ ); + + return ( + <> + + Dashboard + + {content} + + ); +}; + +export default DashboardPage; diff --git a/pages/dashboard/index.tsx b/pages/dashboard/index.tsx deleted file mode 100644 index 6eeffe2..0000000 --- a/pages/dashboard/index.tsx +++ /dev/null @@ -1,31 +0,0 @@ -import React, { useContext } from 'react'; -import Head from 'next/head'; -import { Button, PageHeader } from 'antd'; -import { UserContext } from 'app/contexts/user.context'; - -const DashboardPage: React.FC = () => { - const { logout } = useContext(UserContext); - - return ( - <> - - Dashboard - - -
- logout()}> - Log out - , - ]} - /> -
- - ); -}; - -export default DashboardPage; diff --git a/pages/users/[userProfileId].tsx b/pages/users/[userProfileId].tsx index 2857301..cbc4944 100644 --- a/pages/users/[userProfileId].tsx +++ b/pages/users/[userProfileId].tsx @@ -3,21 +3,7 @@ import Head from 'next/head'; import { useRouter } from 'next/router'; import { request } from 'graphql-request'; import useSWR from 'swr'; -import { - Spin, - Typography, - PageHeader, - Button, - Tag, - Avatar, - Tabs, - Card, - Col, - Row, -} from 'antd'; - -const { Paragraph, Title } = Typography; -const { TabPane } = Tabs; +import { Spin, PageHeader, Button, Tag } from 'antd'; const ProfilePage: React.FC = () => { const router = useRouter(); @@ -40,45 +26,24 @@ const ProfilePage: React.FC = () => { return ( <> - Profile + {data ? data.profile.username : 'Profile'}
- {!data?.profile && } - {data?.profile && ( + {!data && } + {data && (
router.back()} - avatar={{ src: data?.profile.avatarUrl }} - title={data?.profile.username} + avatar={{ src: data.profile.avatarUrl, size: 64 }} + title={ +
+ {data.profile.username} +
+ } tags={{data?.profile.localeCode}} subTitle="Profile" extra={} - > - Profile Descriptions Coming Soon -
- - - - left - - - {data?.profile.username} -
- - Tab 1} key="1"> - Tab 1 - - Tab 2} key="2"> - Tab 2 - - -
- -
+ />
)}