Skip to content

Commit

Permalink
feat: implement subscription setting
Browse files Browse the repository at this point in the history
  • Loading branch information
boojack committed Sep 24, 2023
1 parent 46fa546 commit 24fe368
Show file tree
Hide file tree
Showing 18 changed files with 381 additions and 169 deletions.
36 changes: 8 additions & 28 deletions frontend/web/src/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,52 +2,32 @@ import { useColorScheme } from "@mui/joy";
import { useEffect, useState } from "react";
import { Outlet } from "react-router-dom";
import DemoBanner from "./components/DemoBanner";
import { workspaceServiceClient } from "./grpcweb";
import { workspaceService } from "./services";
import useUserStore from "./stores/v1/user";
import { WorkspaceSetting } from "./types/proto/api/v2/workspace_service";
import useWorkspaceStore from "./stores/v1/workspace";

function App() {
const { mode: colorScheme } = useColorScheme();
const userStore = useUserStore();
const [workspaceSetting, setWorkspaceSetting] = useState<WorkspaceSetting>(WorkspaceSetting.fromPartial({}));
const workspaceStore = useWorkspaceStore();
const [loading, setLoading] = useState(true);

useEffect(() => {
const initialState = async () => {
(async () => {
try {
await workspaceService.initialState();
await Promise.all([workspaceStore.fetchWorkspaceProfile(), workspaceStore.fetchWorkspaceSetting(), userStore.fetchCurrentUser()]);
} catch (error) {
// do nothing
// do nth
}

try {
const { setting } = await workspaceServiceClient.getWorkspaceSetting({});
if (setting) {
setWorkspaceSetting(setting);
}
} catch (error) {
// do nothing
}

try {
await userStore.fetchCurrentUser();
} catch (error) {
// do nothing.
}

setLoading(false);
};

initialState();
})();
}, []);

useEffect(() => {
const styleEl = document.createElement("style");
styleEl.innerHTML = workspaceSetting.customStyle;
styleEl.innerHTML = workspaceStore.setting.customStyle;
styleEl.setAttribute("type", "text/css");
document.body.insertAdjacentElement("beforeend", styleEl);
}, [workspaceSetting.customStyle]);
}, [workspaceStore.setting.customStyle]);

useEffect(() => {
const root = document.documentElement;
Expand Down
8 changes: 3 additions & 5 deletions frontend/web/src/components/DemoBanner.tsx
Original file line number Diff line number Diff line change
@@ -1,11 +1,9 @@
import { workspaceService } from "../services";
import useWorkspaceStore from "@/stores/v1/workspace";
import Icon from "./Icon";

const DemoBanner: React.FC = () => {
const {
workspaceProfile: { mode },
} = workspaceService.getState();
const shouldShow = mode === "demo";
const workspaceStore = useWorkspaceStore();
const shouldShow = workspaceStore.profile.mode === "demo";

if (!shouldShow) return null;

Expand Down
11 changes: 10 additions & 1 deletion frontend/web/src/components/Header.tsx
Original file line number Diff line number Diff line change
@@ -1,15 +1,19 @@
import { Avatar } from "@mui/joy";
import { useState } from "react";
import { Link } from "react-router-dom";
import useWorkspaceStore from "@/stores/v1/workspace";
import { PlanType } from "@/types/proto/api/v2/subscription_service";
import * as api from "../helpers/api";
import useUserStore from "../stores/v1/user";
import AboutDialog from "./AboutDialog";
import Icon from "./Icon";
import Dropdown from "./common/Dropdown";

const Header: React.FC = () => {
const workspaceStore = useWorkspaceStore();
const currentUser = useUserStore().getCurrentUser();
const [showAboutDialog, setShowAboutDialog] = useState<boolean>(false);
const profile = workspaceStore.profile;
const isAdmin = currentUser.role === "ADMIN";

const handleSignOutButtonClick = async () => {
Expand All @@ -23,9 +27,14 @@ const Header: React.FC = () => {
<div className="w-full max-w-6xl mx-auto px-3 md:px-12 py-5 flex flex-row justify-between items-center">
<div className="flex flex-row justify-start items-center shrink mr-2">
<Link to="/" className="text-lg cursor-pointer flex flex-row justify-start items-center dark:text-gray-400">
<img src="/logo.png" className="w-8 h-auto mr-2 -mt-0.5 dark:opacity-80" alt="" />
<img id="logo-img" src="/logo.png" className="w-8 h-auto mr-2 -mt-0.5 dark:opacity-80" alt="" />
Slash
</Link>
{profile.plan === PlanType.PRO && (
<span className="ml-1 text-xs px-1.5 leading-5 border rounded-full bg-blue-600 border-blue-700 text-white shadow dark:opacity-70">
PRO
</span>
)}
</div>
<div className="relative flex-shrink-0">
<Dropdown
Expand Down
33 changes: 33 additions & 0 deletions frontend/web/src/components/SubscriptionFAQ.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
import Accordion from "@mui/joy/Accordion";
import AccordionDetails from "@mui/joy/AccordionDetails";
import AccordionGroup from "@mui/joy/AccordionGroup";
import AccordionSummary from "@mui/joy/AccordionSummary";

const SubscriptionFAQ = () => {
return (
<div className="w-full flex flex-col justify-center items-center">
<h2 className="text-2xl font-semibold mb-8 dark:text-gray-400">Frequently Asked Questions</h2>
<AccordionGroup className="w-full max-w-2xl">
<Accordion>
<AccordionSummary>Can I use the Free plan in my team?</AccordionSummary>
<AccordionDetails>
Of course you can. In the free plan, you can invite up to 5 members to your team. If you need more, you can upgrade to the Pro
plan.
</AccordionDetails>
</Accordion>
<Accordion>
<AccordionSummary>How many devices can the license key be used on?</AccordionSummary>
<AccordionDetails>{`It's unlimited for now, but please don't abuse it.`}</AccordionDetails>
</Accordion>
<Accordion>
<AccordionSummary>{`Can I get a refund if Slash doesn't meet my needs?`}</AccordionSummary>
<AccordionDetails>
Yes, absolutely! You can send a email to me at `stevenlgtm@gmail.com`. I will refund you as soon as possible.
</AccordionDetails>
</Accordion>
</AccordionGroup>
</div>
);
};

export default SubscriptionFAQ;
29 changes: 14 additions & 15 deletions frontend/web/src/components/setting/PreferenceSection.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,20 @@ const PreferenceSection: React.FC = () => {
<>
<div className="w-full flex flex-col justify-start items-start gap-y-2">
<p className="text-base font-semibold leading-6 text-gray-900 dark:text-gray-500">Preference</p>
<div className="w-full flex flex-row justify-between items-center">
<div className="flex flex-row justify-start items-center gap-x-1">
<span className="dark:text-gray-400">Color Theme</span>
</div>
<Select defaultValue={colorTheme} onChange={(_, value) => handleSelectColorTheme(value as UserSetting_ColorTheme)}>
{colorThemeOptions.map((option) => {
return (
<Option key={option.value} value={option.value}>
{option.label}
</Option>
);
})}
</Select>
</div>
{releaseGuard() && (
<div className="w-full flex flex-row justify-between items-center">
<div className="flex flex-row justify-start items-center gap-x-1">
Expand All @@ -79,21 +93,6 @@ const PreferenceSection: React.FC = () => {
</Select>
</div>
)}
<div className="w-full flex flex-row justify-between items-center">
<div className="flex flex-row justify-start items-center gap-x-1">
<span className="dark:text-gray-400">Color Theme</span>
<BetaBadge />
</div>
<Select defaultValue={colorTheme} onChange={(_, value) => handleSelectColorTheme(value as UserSetting_ColorTheme)}>
{colorThemeOptions.map((option) => {
return (
<Option key={option.value} value={option.value}>
{option.label}
</Option>
);
})}
</Select>
</div>
</div>
</>
);
Expand Down
40 changes: 15 additions & 25 deletions frontend/web/src/components/setting/WorkspaceSection.tsx
Original file line number Diff line number Diff line change
@@ -1,25 +1,17 @@
import { Button, Checkbox, Textarea } from "@mui/joy";
import { isEqual } from "lodash-es";
import { useEffect, useRef, useState } from "react";
import { useRef, useState } from "react";
import toast from "react-hot-toast";
import { workspaceServiceClient } from "@/grpcweb";
import { releaseGuard } from "@/helpers/utils";
import useWorkspaceStore from "@/stores/v1/workspace";
import { WorkspaceSetting } from "@/types/proto/api/v2/workspace_service";

const WorkspaceSection: React.FC = () => {
const [workspaceSetting, setWorkspaceSetting] = useState<WorkspaceSetting>(WorkspaceSetting.fromPartial({}));
const originalWorkspaceSetting = useRef<WorkspaceSetting>(WorkspaceSetting.fromPartial({}));
const workspaceStore = useWorkspaceStore();
const [workspaceSetting, setWorkspaceSetting] = useState<WorkspaceSetting>(workspaceStore.setting);
const originalWorkspaceSetting = useRef<WorkspaceSetting>(workspaceStore.setting);
const allowSave = !isEqual(originalWorkspaceSetting.current, workspaceSetting);

useEffect(() => {
workspaceServiceClient.getWorkspaceSetting({}).then(({ setting }) => {
if (setting) {
setWorkspaceSetting(setting);
originalWorkspaceSetting.current = setting;
}
});
}, []);

const handleEnableSignUpChange = async (value: boolean) => {
setWorkspaceSetting({
...workspaceSetting,
Expand Down Expand Up @@ -65,18 +57,16 @@ const WorkspaceSection: React.FC = () => {
return (
<div className="w-full flex flex-col justify-start items-start space-y-4">
<p className="text-base font-semibold leading-6 text-gray-900 dark:text-gray-500">Workspace settings</p>
{releaseGuard() && (
<div className="w-full flex flex-col justify-start items-start">
<p className="mt-2 dark:text-gray-400">Custom style</p>
<Textarea
className="w-full mt-2"
minRows={2}
maxRows={5}
value={workspaceSetting.customStyle}
onChange={(event) => handleCustomStyleChange(event.target.value)}
/>
</div>
)}
<div className="w-full flex flex-col justify-start items-start">
<p className="mt-2 dark:text-gray-400">Custom style</p>
<Textarea
className="w-full mt-2"
minRows={2}
maxRows={5}
value={workspaceSetting.customStyle}
onChange={(event) => handleCustomStyleChange(event.target.value)}
/>
</div>
<div className="w-full flex flex-col justify-start items-start">
<Checkbox
label="Enable user signup"
Expand Down
32 changes: 0 additions & 32 deletions frontend/web/src/pages/Setting.tsx

This file was deleted.

12 changes: 5 additions & 7 deletions frontend/web/src/pages/SignIn.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import React, { FormEvent, useEffect, useState } from "react";
import { toast } from "react-hot-toast";
import { useTranslation } from "react-i18next";
import { Link, useNavigate } from "react-router-dom";
import { workspaceService } from "@/services";
import useWorkspaceStore from "@/stores/v1/workspace";
import * as api from "../helpers/api";
import useLoading from "../hooks/useLoading";
import useUserStore from "../stores/v1/user";
Expand All @@ -12,9 +12,7 @@ const SignIn: React.FC = () => {
const { t } = useTranslation();
const navigate = useNavigate();
const userStore = useUserStore();
const {
workspaceProfile: { enableSignup, mode },
} = workspaceService.getState();
const workspaceStore = useWorkspaceStore();
const [email, setEmail] = useState("");
const [password, setPassword] = useState("");
const actionBtnLoadingState = useLoading(false);
Expand All @@ -27,7 +25,7 @@ const SignIn: React.FC = () => {
});
}

if (mode === "demo") {
if (workspaceStore.profile.mode === "demo") {
setEmail("steven@yourselfhosted.com");
setPassword("secret");
}
Expand Down Expand Up @@ -72,7 +70,7 @@ const SignIn: React.FC = () => {
<div className="w-80 max-w-full h-full py-4 flex flex-col justify-start items-center">
<div className="w-full py-4 grow flex flex-col justify-center items-center">
<div className="flex flex-row justify-start items-center w-auto mx-auto gap-y-2 mb-4">
<img src="/logo.png" className="w-12 h-auto mr-2 -mt-1" alt="logo" />
<img id="logo-img" src="/logo.png" className="w-12 h-auto mr-2 -mt-1" alt="logo" />
<span className="text-3xl opacity-80 dark:text-gray-500">Slash</span>
</div>
<form className="w-full mt-6" onSubmit={handleSigninBtnClick}>
Expand Down Expand Up @@ -105,7 +103,7 @@ const SignIn: React.FC = () => {
</Button>
</div>
</form>
{enableSignup && (
{workspaceStore.setting.enableSignup && (
<p className="w-full mt-4 text-sm">
<span className="dark:text-gray-500">{"Don't have an account yet?"}</span>
<Link to="/auth/signup" className="cursor-pointer ml-2 text-blue-600 hover:underline">
Expand Down
10 changes: 4 additions & 6 deletions frontend/web/src/pages/SignUp.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,18 +3,16 @@ import React, { FormEvent, useEffect, useState } from "react";
import { toast } from "react-hot-toast";
import { useTranslation } from "react-i18next";
import { Link, useNavigate } from "react-router-dom";
import useWorkspaceStore from "@/stores/v1/workspace";
import * as api from "../helpers/api";
import useLoading from "../hooks/useLoading";
import { workspaceService } from "../services";
import useUserStore from "../stores/v1/user";

const SignUp: React.FC = () => {
const { t } = useTranslation();
const navigate = useNavigate();
const userStore = useUserStore();
const {
workspaceProfile: { enableSignup },
} = workspaceService.getState();
const workspaceStore = useWorkspaceStore();
const [email, setEmail] = useState("");
const [nickname, setNickname] = useState("");
const [password, setPassword] = useState("");
Expand All @@ -28,7 +26,7 @@ const SignUp: React.FC = () => {
});
}

if (!enableSignup) {
if (!workspaceStore.setting.enableSignup) {
return navigate("/auth", {
replace: true,
});
Expand Down Expand Up @@ -79,7 +77,7 @@ const SignUp: React.FC = () => {
<div className="w-80 max-w-full h-full py-4 flex flex-col justify-start items-center">
<div className="w-full py-4 grow flex flex-col justify-center items-center">
<div className="flex flex-row justify-start items-center w-auto mx-auto gap-y-2 mb-4">
<img src="/logo.png" className="w-12 h-auto mr-2 -mt-1" alt="logo" />
<img id="logo-img" src="/logo.png" className="w-12 h-auto mr-2 -mt-1" alt="logo" />
<span className="text-3xl opacity-80 dark:text-gray-500">Slash</span>
</div>
<p className="w-full text-2xl mt-6 dark:text-gray-500">Create your account</p>
Expand Down
Loading

0 comments on commit 24fe368

Please sign in to comment.