From 2657469964e24ffbeb905024532120395f6e797c Mon Sep 17 00:00:00 2001 From: Mo Tarbin Date: Sun, 30 Jun 2024 18:55:39 -0400 Subject: move to Donetick Org, First commit frontend --- src/views/Settings/APITokenSettings.jsx | 130 ++++++++++ src/views/Settings/NotificationSetting.jsx | 90 +++++++ src/views/Settings/Settings.jsx | 384 +++++++++++++++++++++++++++++ src/views/Settings/Sharing.jsx | 0 src/views/Settings/SharingSettings.jsx | 0 src/views/Settings/ThemeToggle.jsx | 62 +++++ 6 files changed, 666 insertions(+) create mode 100644 src/views/Settings/APITokenSettings.jsx create mode 100644 src/views/Settings/NotificationSetting.jsx create mode 100644 src/views/Settings/Settings.jsx create mode 100644 src/views/Settings/Sharing.jsx create mode 100644 src/views/Settings/SharingSettings.jsx create mode 100644 src/views/Settings/ThemeToggle.jsx (limited to 'src/views/Settings') diff --git a/src/views/Settings/APITokenSettings.jsx b/src/views/Settings/APITokenSettings.jsx new file mode 100644 index 0000000..5bd9887 --- /dev/null +++ b/src/views/Settings/APITokenSettings.jsx @@ -0,0 +1,130 @@ +import { Box, Button, Card, Chip, Divider, Typography } from '@mui/joy' +import moment from 'moment' +import { useContext, useEffect, useState } from 'react' +import { UserContext } from '../../contexts/UserContext' +import { + CreateLongLiveToken, + DeleteLongLiveToken, + GetLongLiveTokens, +} from '../../utils/Fetcher' +import { isPlusAccount } from '../../utils/Helpers' +import TextModal from '../Modals/Inputs/TextModal' + +const APITokenSettings = () => { + const [tokens, setTokens] = useState([]) + const [isGetTokenNameModalOpen, setIsGetTokenNameModalOpen] = useState(false) + const { userProfile, setUserProfile } = useContext(UserContext) + useEffect(() => { + GetLongLiveTokens().then(resp => { + resp.json().then(data => { + setTokens(data.res) + }) + }) + }, []) + + const handleSaveToken = name => { + CreateLongLiveToken(name).then(resp => { + if (resp.ok) { + resp.json().then(data => { + // add the token to the list: + console.log(data) + const newTokens = [...tokens] + newTokens.push(data.res) + setTokens(newTokens) + }) + } + }) + } + + return ( +
+ Long Live Token + + + Create token to use with the API to update things that trigger task or + chores + + {!isPlusAccount(userProfile) && ( + + Not available in Basic Plan + + )} + + {tokens.map(token => ( + + + + {token.name} + + {moment(token.createdAt).fromNow()}( + {moment(token.createdAt).format('lll')}) + + + + {token.token && ( + + )} + + + + + + ))} + + + { + setIsGetTokenNameModalOpen(false) + }} + okText={'Generate Token'} + onSave={handleSaveToken} + /> +
+ ) +} + +export default APITokenSettings diff --git a/src/views/Settings/NotificationSetting.jsx b/src/views/Settings/NotificationSetting.jsx new file mode 100644 index 0000000..4ead3b9 --- /dev/null +++ b/src/views/Settings/NotificationSetting.jsx @@ -0,0 +1,90 @@ +import { Button, Divider, Input, Option, Select, Typography } from '@mui/joy' +import { useContext, useEffect, useState } from 'react' +import { UserContext } from '../../contexts/UserContext' +import { GetUserProfile, UpdateUserDetails } from '../../utils/Fetcher' + +const NotificationSetting = () => { + const { userProfile, setUserProfile } = useContext(UserContext) + useEffect(() => { + if (!userProfile) { + GetUserProfile().then(resp => { + resp.json().then(data => { + setUserProfile(data.res) + setChatID(data.res.chatID) + }) + }) + } + }, []) + const [chatID, setChatID] = useState(userProfile?.chatID) + + return ( +
+ Notification Settings + + Manage your notification settings + + + + + You need to initiate a message to the bot in order for the Telegram + notification to work{' '} + + Click here + {' '} + to start a chat + + + setChatID(e.target.value)} + placeholder='User ID / Chat ID' + sx={{ + width: '200px', + }} + /> + + If you don't know your Chat ID, start chat with userinfobot and it will + send you your Chat ID.{' '} + + Click here + {' '} + to start chat with userinfobot{' '} + + + +
+ ) +} + +export default NotificationSetting diff --git a/src/views/Settings/Settings.jsx b/src/views/Settings/Settings.jsx new file mode 100644 index 0000000..d612eec --- /dev/null +++ b/src/views/Settings/Settings.jsx @@ -0,0 +1,384 @@ +import { + Box, + Button, + Card, + Chip, + CircularProgress, + Container, + Divider, + Input, + Typography, +} from '@mui/joy' +import moment from 'moment' +import { useContext, useEffect, useState } from 'react' +import { UserContext } from '../../contexts/UserContext' +import Logo from '../../Logo' +import { + AcceptCircleMemberRequest, + CancelSubscription, + DeleteCircleMember, + GetAllCircleMembers, + GetCircleMemberRequests, + GetSubscriptionSession, + GetUserCircle, + GetUserProfile, + JoinCircle, + LeaveCircle, +} from '../../utils/Fetcher' +import APITokenSettings from './APITokenSettings' +import NotificationSetting from './NotificationSetting' +import ThemeToggle from './ThemeToggle' + +const Settings = () => { + const { userProfile, setUserProfile } = useContext(UserContext) + const [userCircles, setUserCircles] = useState([]) + const [circleMemberRequests, setCircleMemberRequests] = useState([]) + const [circleInviteCode, setCircleInviteCode] = useState('') + const [circleMembers, setCircleMembers] = useState([]) + useEffect(() => { + GetUserProfile().then(resp => { + resp.json().then(data => { + setUserProfile(data.res) + }) + }) + GetUserCircle().then(resp => { + resp.json().then(data => { + setUserCircles(data.res ? data.res : []) + }) + }) + GetCircleMemberRequests().then(resp => { + resp.json().then(data => { + setCircleMemberRequests(data.res ? data.res : []) + }) + }) + GetAllCircleMembers() + .then(res => res.json()) + .then(data => { + setCircleMembers(data.res ? data.res : []) + }) + }, []) + + useEffect(() => { + const hash = window.location.hash + if (hash) { + const sharingSection = document.getElementById( + window.location.hash.slice(1), + ) + if (sharingSection) { + sharingSection.scrollIntoView({ behavior: 'smooth' }) + } + } + }, []) + + const getSubscriptionDetails = () => { + if (userProfile?.subscription === 'active') { + return `You are currently subscribed to the Plus plan. Your subscription will renew on ${moment( + userProfile?.expiration, + ).format('MMM DD, YYYY')}.` + } else if (userProfile?.subscription === 'canceled') { + return `You have cancelled your subscription. Your account will be downgraded to the Free plan on ${moment( + userProfile?.expiration, + ).format('MMM DD, YYYY')}.` + } else { + return `You are currently on the Free plan. Upgrade to the Plus plan to unlock more features.` + } + } + const getSubscriptionStatus = () => { + if (userProfile?.subscription === 'active') { + return `Plus` + } else if (userProfile?.subscription === 'canceled') { + if (moment().isBefore(userProfile?.expiration)) { + return `Plus(until ${moment(userProfile?.expiration).format( + 'MMM DD, YYYY', + )})` + } + return `Free` + } else { + return `Free` + } + } + + if (userProfile === null) { + return ( + + + + + + + + ) + } + return ( + +
+ Sharing settings + + + Your account is automatically connected to a Circle when you create or + join one. Easily invite friends by sharing the unique Circle code or + link below. You'll receive a notification below when someone requests + to join your Circle. + + + {userCircles[0]?.userRole === 'member' + ? `You part of ${userCircles[0]?.name} ` + : `You circle code is:`} + + + + + {userCircles.length > 0 && userCircles[0]?.userRole === 'member' && ( + + )} + + Circle Members + {circleMembers.map(member => ( + + + + + {member.displayName.charAt(0).toUpperCase() + + member.displayName.slice(1)} + {member.userId === userProfile.id ? '(You)' : ''}{' '} + + {' '} + {member.isActive ? member.role : 'Pending Approval'} + + + {member.isActive ? ( + + Joined on {moment(member.createdAt).format('MMM DD, YYYY')} + + ) : ( + + Request to join{' '} + {moment(member.updatedAt).format('MMM DD, YYYY')} + + )} + + {member.userId !== userProfile.id && member.isActive && ( + + )} + + + ))} + + {circleMemberRequests.length > 0 && ( + Circle Member Requests + )} + {circleMemberRequests.map(request => ( + + + {request.displayName} wants to join your circle. + + + + ))} + or + + + if want to join someone else's Circle? Ask them for their unique + Circle code or join link. Enter the code below to join their Circle. + + + + Enter Circle code: + setCircleInviteCode(e.target.value)} + size='lg' + sx={{ + width: '220px', + mb: 1, + }} + /> + + +
+ +
+ Account Settings + + + Change your account settings, including your password, display name + + + Account Type : {getSubscriptionStatus()} + + {getSubscriptionDetails()} + + + + {userProfile?.subscription === 'active' && ( + + )} + +
+ + +
+ Theme preferences + + + Choose how the site looks to you. Select a single theme, or sync with + your system and automatically switch between day and night themes. + + +
+
+ ) +} + +export default Settings diff --git a/src/views/Settings/Sharing.jsx b/src/views/Settings/Sharing.jsx new file mode 100644 index 0000000..e69de29 diff --git a/src/views/Settings/SharingSettings.jsx b/src/views/Settings/SharingSettings.jsx new file mode 100644 index 0000000..e69de29 diff --git a/src/views/Settings/ThemeToggle.jsx b/src/views/Settings/ThemeToggle.jsx new file mode 100644 index 0000000..6ff33f1 --- /dev/null +++ b/src/views/Settings/ThemeToggle.jsx @@ -0,0 +1,62 @@ +import useStickyState from '@/hooks/useStickyState' +import { + DarkModeOutlined, + LaptopOutlined, + LightModeOutlined, +} from '@mui/icons-material' +import { + Button, + FormControl, + FormLabel, + ToggleButtonGroup, + useColorScheme, +} from '@mui/joy' + +const ELEMENTID = 'select-theme-mode' + +const ThemeToggle = () => { + const { mode, setMode } = useColorScheme() + const [themeMode, setThemeMode] = useStickyState(mode, 'themeMode') + + const handleThemeModeChange = (_, newThemeMode) => { + if (!newThemeMode) return + setThemeMode(newThemeMode) + setMode(newThemeMode) + } + + const FormThemeModeToggleLabel = () => ( + + Theme mode + + ) + + return ( + + +
+ + + + + +
+
+ ) +} + +export default ThemeToggle -- cgit