-
-
Notifications
You must be signed in to change notification settings - Fork 232
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #252 from djyde/subscription
Subscription
- Loading branch information
Showing
21 changed files
with
851 additions
and
48 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -3,7 +3,7 @@ import { useMutation, useQuery } from "react-query" | |
import { useRouter } from "next/router" | ||
import { AiOutlineLogout, AiOutlineSetting, AiOutlineFileText, AiOutlineAlert, AiOutlinePlus, AiOutlineComment, AiOutlineCode, AiOutlineRight, AiOutlineDown, AiOutlineFile, AiOutlineQuestion, AiOutlineQuestionCircle } from 'react-icons/ai' | ||
import { signout, signOut } from "next-auth/client" | ||
import { Anchor, AppShell, Avatar, Badge, Box, Button, Code, Group, Header, Menu, Modal, Navbar, NavLink, ScrollArea, Select, Space, Stack, Switch, Text, TextInput, Title } from "@mantine/core" | ||
import { Anchor, AppShell, Avatar, Badge, Box, Button, Code, Grid, Group, Header, List, Menu, Modal, Navbar, NavLink, Paper, Progress, ScrollArea, Select, Space, Stack, Switch, Text, TextInput, Title } from "@mantine/core" | ||
import Link from "next/link" | ||
import type { ProjectServerSideProps } from "../pages/dashboard/project/[projectId]/settings" | ||
import { modals } from "@mantine/modals" | ||
|
@@ -13,6 +13,8 @@ import { apiClient } from "../utils.client" | |
import { useForm } from "react-hook-form" | ||
import { MainLayoutData } from "../service/viewData.service" | ||
import { Head } from "./Head" | ||
import dayjs from "dayjs" | ||
import { usageLimitation } from "../config.common" | ||
|
||
// From https://stackoverflow.com/questions/46155/how-to-validate-an-email-address-in-javascript | ||
function validateEmail(email) { | ||
|
@@ -55,6 +57,25 @@ export function MainLayout(props: { | |
}, | ||
}) | ||
|
||
const downgradePlanMutation = useMutation(async () => { | ||
await apiClient.delete('/subscription') | ||
}, { | ||
onSuccess() { | ||
notifications.show({ | ||
title: 'Success', | ||
message: 'Downgrade success', | ||
color: 'green' | ||
}) | ||
}, | ||
onError() { | ||
notifications.show({ | ||
title: 'Error', | ||
message: 'Something went wrong, please contact [email protected]', | ||
color: 'red' | ||
}) | ||
} | ||
}) | ||
|
||
const updateNewCommentNotification = useMutation(updateUserSettings, { | ||
onSuccess() { | ||
notifications.show({ | ||
|
@@ -209,11 +230,14 @@ export function MainLayout(props: { | |
}, []) | ||
|
||
const badge = React.useMemo(() => { | ||
if (!props.config.isHosted) { | ||
return <Badge color="green" size="xs">OSS</Badge> | ||
if (props.subscription.isActived) { | ||
return <Badge color="green" size="xs">PRO</Badge> | ||
} | ||
|
||
return <Badge color="green" size="xs">PRO</Badge> | ||
if (props.config.isHosted) { | ||
return <Badge color="gray" size="xs">OSS</Badge> | ||
} | ||
return <Badge color="green" size="xs">FREE</Badge> | ||
}, []) | ||
|
||
const header = React.useMemo(() => { | ||
|
@@ -242,12 +266,43 @@ export function MainLayout(props: { | |
<Group spacing={4}> | ||
<Button onClick={_ => { | ||
openUserModal() | ||
}} size="xs" rightIcon={<AiOutlineRight />} variant='subtle'>{props.session.user.name}</Button> | ||
}} size="xs" rightIcon={<AiOutlineRight />} variant='subtle'>{props.session.user.name} {badge}</Button> | ||
</Group> | ||
</Group> | ||
) | ||
}, []) | ||
|
||
const usageBoard = React.useMemo(() => { | ||
return ( | ||
<> | ||
<Text size="sm" weight={900}> | ||
Usage (per month) | ||
</Text> | ||
<Stack spacing={4}> | ||
<Group spacing={4}> | ||
<Text weight={500} size="sm">Sites:</Text> | ||
<Text size='sm'> | ||
{`${props.usage.projectCount} / ${usageLimitation['create_site']}`} | ||
</Text> | ||
</Group> | ||
|
||
<Group spacing={4}> | ||
<Text weight={500} size="sm">Approve comments:</Text> | ||
<Text size='sm'> | ||
{`${props.usage.approveCommentUsage} / ${usageLimitation['approve_comment']}`} | ||
</Text> | ||
</Group> | ||
<Group spacing={4}> | ||
<Text weight={500} size="sm">Quick Approve:</Text> | ||
<Text size='sm'> | ||
{`${props.usage.quickApproveUsage} / ${usageLimitation['quick_approve']}`} | ||
</Text> | ||
</Group> | ||
</Stack> | ||
</> | ||
) | ||
}, []) | ||
|
||
return ( | ||
<> | ||
<Head title={`${props.project.title} - Cusdis`} /> | ||
|
@@ -273,7 +328,7 @@ export function MainLayout(props: { | |
} | ||
}} | ||
> | ||
<Modal opened={isUserPannelOpen} onClose={closeUserModal} | ||
<Modal opened={isUserPannelOpen} size="lg" onClose={closeUserModal} | ||
title="User Settings" | ||
> | ||
<Stack> | ||
|
@@ -298,11 +353,81 @@ export function MainLayout(props: { | |
<Text weight={500} size="sm">Display name</Text> | ||
<TextInput placeholder={props.userInfo.name} {...userSettingsForm.register("displayName")} size="sm" /> | ||
</Stack> | ||
{/* <Stack spacing={8}> | ||
<Text weight={500} size="sm">Subscription </Text> | ||
<Text size="sm">Current plan: {badge}</Text> | ||
<Anchor size="sm">Manage subscription</Anchor> | ||
</Stack> */} | ||
{props.config.checkout.enabled && ( | ||
<> | ||
{usageBoard} | ||
<Stack spacing={8}> | ||
<Text weight={900} size="sm">Subscription </Text> | ||
<Grid> | ||
<Grid.Col span={6}> | ||
<Paper sx={theme => ({ | ||
border: '1px solid #eaeaea', | ||
padding: theme.spacing.md | ||
})}> | ||
<Stack> | ||
<Title order={4}> | ||
Free | ||
</Title> | ||
<List size='sm' sx={{ | ||
}}> | ||
<List.Item> | ||
Up to 1 site | ||
</List.Item> | ||
<List.Item> | ||
10 Quick Approve / month | ||
</List.Item> | ||
<List.Item> | ||
100 approved comments / month | ||
</List.Item> | ||
</List> | ||
{!props.subscription.isActived || props.subscription.status === 'cancelled' ? ( | ||
<Button disabled size="xs">Current plan</Button> | ||
) : ( | ||
<Button size="xs" variant={'outline'} loading={downgradePlanMutation.isLoading} onClick={_ => { | ||
if (window.confirm('Are you sure to downgrade?')) { | ||
downgradePlanMutation.mutate() | ||
} | ||
}}>Downgrade</Button> | ||
)} | ||
</Stack> | ||
</Paper> | ||
</Grid.Col> | ||
<Grid.Col span={6}> | ||
<Paper sx={theme => ({ | ||
border: '1px solid #eaeaea', | ||
padding: theme.spacing.md | ||
})}> | ||
<Stack> | ||
<Title order={4}> | ||
Pro | ||
</Title> | ||
<List size='sm' sx={{ | ||
}}> | ||
<List.Item> | ||
Unlimited sites | ||
</List.Item> | ||
<List.Item> | ||
Unlimited Quick Approve | ||
</List.Item> | ||
<List.Item> | ||
Unlimited approved comments | ||
</List.Item> | ||
</List> | ||
{props.subscription.isActived ? ( | ||
<> | ||
<Button size="xs" component="a" href={props.subscription.updatePaymentMethodUrl}>Manage payment method</Button> | ||
{props.subscription.status === 'cancelled' && (<Text size='xs' align='center'>Expire on {dayjs(props.subscription.endAt).format('YYYY/MM/DD')}</Text>)} | ||
</> | ||
) : ( | ||
<Button size='xs' component="a" href={`${props.config.checkout.url}?checkout[custom][user_id]=${props.session.uid}`}>Upgrade $5/month</Button> | ||
)} | ||
</Stack> | ||
</Paper> | ||
</Grid.Col> | ||
</Grid> | ||
</Stack> | ||
</> | ||
)} | ||
<Button loading={updateUserSettingsMutation.isLoading} onClick={onClickSaveUserSettings}>Save</Button> | ||
<Button onClick={_ => signOut()} variant={'outline'} color='red'> | ||
Logout | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,40 @@ | ||
import { | ||
Box, | ||
Container, | ||
Flex, | ||
Link, | ||
Menu, | ||
MenuButton, | ||
MenuItem, | ||
MenuList, | ||
Spacer, | ||
} from "@chakra-ui/react" | ||
import React from "react" | ||
import { signOut } from "next-auth/client" | ||
import { UserSession } from "../service" | ||
|
||
export function Navbar(props: { session: UserSession }) { | ||
return ( | ||
<Box py={4}> | ||
<Container maxWidth={"5xl"}> | ||
<Flex> | ||
<Box> | ||
<Link fontWeight="bold" fontSize="xl" href="/dashboard"> | ||
Cusdis | ||
</Link> | ||
</Box> | ||
<Spacer /> | ||
<Box> | ||
<Menu> | ||
<MenuButton>{props.session.user.name}</MenuButton> | ||
<MenuList> | ||
<MenuItem><Link width="100%" href="/user">Settings</Link></MenuItem> | ||
<MenuItem onClick={() => signOut()}>Logout</MenuItem> | ||
</MenuList> | ||
</Menu> | ||
</Box> | ||
</Flex> | ||
</Container> | ||
</Box> | ||
) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,11 @@ | ||
export enum UsageLabel { | ||
ApproveComment = 'approve_comment', | ||
QuickApprove = 'quick_approve', | ||
CreateSite = 'create_site' | ||
} | ||
|
||
export const usageLimitation = { | ||
[UsageLabel.ApproveComment]: 100, | ||
[UsageLabel.QuickApprove]: 10, | ||
[UsageLabel.CreateSite]: 1 | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.