mirror of
https://github.com/rzmk/learnhouse.git
synced 2025-12-19 04:19:25 +00:00
feat: add payments feature access based on config
This commit is contained in:
parent
5a746a946d
commit
546e8a5f98
19 changed files with 98 additions and 43 deletions
|
|
@ -40,7 +40,6 @@ class AssignmentOrgConfig(BaseModel):
|
||||||
|
|
||||||
class PaymentOrgConfig(BaseModel):
|
class PaymentOrgConfig(BaseModel):
|
||||||
enabled: bool = True
|
enabled: bool = True
|
||||||
stripe_key: str = ""
|
|
||||||
|
|
||||||
|
|
||||||
class DiscussionOrgConfig(BaseModel):
|
class DiscussionOrgConfig(BaseModel):
|
||||||
|
|
@ -91,7 +90,7 @@ class OrgCloudConfig(BaseModel):
|
||||||
|
|
||||||
# Main Config
|
# Main Config
|
||||||
class OrganizationConfigBase(BaseModel):
|
class OrganizationConfigBase(BaseModel):
|
||||||
config_version: str = "1.1"
|
config_version: str = "1.2"
|
||||||
general: OrgGeneralConfig
|
general: OrgGeneralConfig
|
||||||
features: OrgFeatureConfig
|
features: OrgFeatureConfig
|
||||||
cloud: OrgCloudConfig
|
cloud: OrgCloudConfig
|
||||||
|
|
|
||||||
|
|
@ -330,7 +330,7 @@ def install_create_organization(org_object: OrganizationCreate, db_session: Sess
|
||||||
|
|
||||||
# Org Config
|
# Org Config
|
||||||
org_config = OrganizationConfigBase(
|
org_config = OrganizationConfigBase(
|
||||||
config_version="1.1",
|
config_version="1.2",
|
||||||
general=OrgGeneralConfig(
|
general=OrgGeneralConfig(
|
||||||
enabled=True,
|
enabled=True,
|
||||||
color="normal",
|
color="normal",
|
||||||
|
|
@ -345,7 +345,7 @@ def install_create_organization(org_object: OrganizationCreate, db_session: Sess
|
||||||
storage=StorageOrgConfig(enabled=True, limit=0),
|
storage=StorageOrgConfig(enabled=True, limit=0),
|
||||||
ai=AIOrgConfig(enabled=True, limit=0, model="gpt-4o-mini"),
|
ai=AIOrgConfig(enabled=True, limit=0, model="gpt-4o-mini"),
|
||||||
assignments=AssignmentOrgConfig(enabled=True, limit=0),
|
assignments=AssignmentOrgConfig(enabled=True, limit=0),
|
||||||
payments=PaymentOrgConfig(enabled=True, stripe_key=""),
|
payments=PaymentOrgConfig(enabled=False),
|
||||||
discussions=DiscussionOrgConfig(enabled=True, limit=0),
|
discussions=DiscussionOrgConfig(enabled=True, limit=0),
|
||||||
analytics=AnalyticsOrgConfig(enabled=True, limit=0),
|
analytics=AnalyticsOrgConfig(enabled=True, limit=0),
|
||||||
collaboration=CollaborationOrgConfig(enabled=True, limit=0),
|
collaboration=CollaborationOrgConfig(enabled=True, limit=0),
|
||||||
|
|
|
||||||
|
|
@ -8,7 +8,7 @@ from src.services.install.install import (
|
||||||
install_default_elements,
|
install_default_elements,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
# TODO: Depreceated and need to be removed and remade
|
||||||
async def create_initial_data_for_tests(db_session: Session):
|
async def create_initial_data_for_tests(db_session: Session):
|
||||||
# Install default elements
|
# Install default elements
|
||||||
await install_default_elements({}, db_session)
|
await install_default_elements({}, db_session)
|
||||||
|
|
|
||||||
|
|
@ -10,8 +10,7 @@ import { useOrg } from '@components/Contexts/OrgContext'
|
||||||
import PaymentsConfigurationPage from '@components/Dashboard/Pages/Payments/PaymentsConfigurationPage'
|
import PaymentsConfigurationPage from '@components/Dashboard/Pages/Payments/PaymentsConfigurationPage'
|
||||||
import PaymentsProductPage from '@components/Dashboard/Pages/Payments/PaymentsProductPage'
|
import PaymentsProductPage from '@components/Dashboard/Pages/Payments/PaymentsProductPage'
|
||||||
import PaymentsCustomersPage from '@components/Dashboard/Pages/Payments/PaymentsCustomersPage'
|
import PaymentsCustomersPage from '@components/Dashboard/Pages/Payments/PaymentsCustomersPage'
|
||||||
|
import useFeatureFlag from '@components/Hooks/useFeatureFlag'
|
||||||
|
|
||||||
|
|
||||||
export type PaymentsParams = {
|
export type PaymentsParams = {
|
||||||
subpage: string
|
subpage: string
|
||||||
|
|
@ -25,10 +24,27 @@ function PaymentsPage({ params }: { params: PaymentsParams }) {
|
||||||
const [H1Label, setH1Label] = useState('')
|
const [H1Label, setH1Label] = useState('')
|
||||||
const [H2Label, setH2Label] = useState('')
|
const [H2Label, setH2Label] = useState('')
|
||||||
|
|
||||||
|
const isPaymentsEnabled = useFeatureFlag({
|
||||||
|
path: ['features', 'payments', 'enabled'],
|
||||||
|
defaultValue: false
|
||||||
|
})
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
handleLabels()
|
handleLabels()
|
||||||
}, [selectedSubPage])
|
}, [selectedSubPage])
|
||||||
|
|
||||||
|
if (!isPaymentsEnabled) {
|
||||||
|
return (
|
||||||
|
<div className="h-screen w-full bg-[#f8f8f8] flex items-center justify-center p-4">
|
||||||
|
<div className="bg-white p-6 rounded-lg shadow-md text-center max-w-md">
|
||||||
|
<h2 className="text-xl font-bold mb-4">Payments Not Available</h2>
|
||||||
|
<p className="text-gray-600">The payments feature is not enabled for this organization.</p>
|
||||||
|
<p className="text-gray-600 mt-2">Please contact your administrator to enable payments.</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
function handleLabels() {
|
function handleLabels() {
|
||||||
if (selectedSubPage === 'general') {
|
if (selectedSubPage === 'general') {
|
||||||
setH1Label('Payments')
|
setH1Label('Payments')
|
||||||
|
|
|
||||||
|
|
@ -11,11 +11,13 @@ import UserAvatar from '../../Objects/UserAvatar'
|
||||||
import AdminAuthorization from '@components/Security/AdminAuthorization'
|
import AdminAuthorization from '@components/Security/AdminAuthorization'
|
||||||
import { useLHSession } from '@components/Contexts/LHSessionContext'
|
import { useLHSession } from '@components/Contexts/LHSessionContext'
|
||||||
import { getUriWithOrg, getUriWithoutOrg } from '@services/config/config'
|
import { getUriWithOrg, getUriWithoutOrg } from '@services/config/config'
|
||||||
|
import useFeatureFlag from '@components/Hooks/useFeatureFlag'
|
||||||
|
|
||||||
function DashLeftMenu() {
|
function DashLeftMenu() {
|
||||||
const org = useOrg() as any
|
const org = useOrg() as any
|
||||||
const session = useLHSession() as any
|
const session = useLHSession() as any
|
||||||
const [loading, setLoading] = React.useState(true)
|
const [loading, setLoading] = React.useState(true)
|
||||||
|
const isPaymentsEnabled = useFeatureFlag({ path: ['features', 'payments', 'enabled'], defaultValue: false })
|
||||||
|
|
||||||
function waitForEverythingToLoad() {
|
function waitForEverythingToLoad() {
|
||||||
if (org && session) {
|
if (org && session) {
|
||||||
|
|
@ -112,6 +114,7 @@ function DashLeftMenu() {
|
||||||
<Users size={18} />
|
<Users size={18} />
|
||||||
</Link>
|
</Link>
|
||||||
</ToolTip>
|
</ToolTip>
|
||||||
|
{isPaymentsEnabled && (
|
||||||
<ToolTip content={'Payments'} slateBlack sideOffset={8} side="right">
|
<ToolTip content={'Payments'} slateBlack sideOffset={8} side="right">
|
||||||
<Link
|
<Link
|
||||||
className="bg-white/5 rounded-lg p-2 hover:bg-white/10 transition-all ease-linear"
|
className="bg-white/5 rounded-lg p-2 hover:bg-white/10 transition-all ease-linear"
|
||||||
|
|
@ -120,6 +123,7 @@ function DashLeftMenu() {
|
||||||
<BadgeDollarSign size={18} />
|
<BadgeDollarSign size={18} />
|
||||||
</Link>
|
</Link>
|
||||||
</ToolTip>
|
</ToolTip>
|
||||||
|
)}
|
||||||
<ToolTip
|
<ToolTip
|
||||||
content={'Organization'}
|
content={'Organization'}
|
||||||
slateBlack
|
slateBlack
|
||||||
|
|
|
||||||
|
|
@ -12,7 +12,7 @@ import { useRouter } from 'next/navigation'
|
||||||
import { useOrg } from '@components/Contexts/OrgContext'
|
import { useOrg } from '@components/Contexts/OrgContext'
|
||||||
import { useLHSession } from '@components/Contexts/LHSessionContext'
|
import { useLHSession } from '@components/Contexts/LHSessionContext'
|
||||||
import { getOrgLogoMediaDirectory, getOrgThumbnailMediaDirectory } from '@services/media/media'
|
import { getOrgLogoMediaDirectory, getOrgThumbnailMediaDirectory } from '@services/media/media'
|
||||||
import { Tabs, TabsContent, TabsList, TabsTrigger } from "@components/Ui/tabs"
|
import { Tabs, TabsContent, TabsList, TabsTrigger } from "@components/ui/tabs"
|
||||||
import { Toaster, toast } from 'react-hot-toast';
|
import { Toaster, toast } from 'react-hot-toast';
|
||||||
import { constructAcceptValue } from '@/lib/constants';
|
import { constructAcceptValue } from '@/lib/constants';
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -10,8 +10,8 @@ import toast from 'react-hot-toast';
|
||||||
import useSWR, { mutate } from 'swr';
|
import useSWR, { mutate } from 'swr';
|
||||||
import Modal from '@components/Objects/StyledElements/Modal/Modal';
|
import Modal from '@components/Objects/StyledElements/Modal/Modal';
|
||||||
import ConfirmationModal from '@components/Objects/StyledElements/ConfirmationModal/ConfirmationModal';
|
import ConfirmationModal from '@components/Objects/StyledElements/ConfirmationModal/ConfirmationModal';
|
||||||
import { Button } from '@components/Ui/button';
|
import { Button } from '@components/ui/button';
|
||||||
import { Alert, AlertDescription, AlertTitle } from '@components/Ui/alert';
|
import { Alert, AlertDescription, AlertTitle } from '@components/ui/alert';
|
||||||
import { useRouter } from 'next/navigation';
|
import { useRouter } from 'next/navigation';
|
||||||
import { getUriWithoutOrg } from '@services/config/config';
|
import { getUriWithoutOrg } from '@services/config/config';
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -9,9 +9,9 @@ import {
|
||||||
TableHead,
|
TableHead,
|
||||||
TableHeader,
|
TableHeader,
|
||||||
TableRow,
|
TableRow,
|
||||||
} from "@components/Ui/table"
|
} from "@components/ui/table"
|
||||||
import { getOrgCustomers } from '@services/payments/payments'
|
import { getOrgCustomers } from '@services/payments/payments'
|
||||||
import { Badge } from '@components/Ui/badge'
|
import { Badge } from '@components/ui/badge'
|
||||||
import PageLoading from '@components/Objects/Loaders/PageLoading'
|
import PageLoading from '@components/Objects/Loaders/PageLoading'
|
||||||
import { RefreshCcw, SquareCheck } from 'lucide-react'
|
import { RefreshCcw, SquareCheck } from 'lucide-react'
|
||||||
import { getUserAvatarMediaDirectory } from '@services/media/media'
|
import { getUserAvatarMediaDirectory } from '@services/media/media'
|
||||||
|
|
|
||||||
|
|
@ -9,14 +9,14 @@ import { Plus, Pencil, Info, RefreshCcw, SquareCheck, ChevronDown, ChevronUp, Ar
|
||||||
import Modal from '@components/Objects/StyledElements/Modal/Modal';
|
import Modal from '@components/Objects/StyledElements/Modal/Modal';
|
||||||
import ConfirmationModal from '@components/Objects/StyledElements/ConfirmationModal/ConfirmationModal';
|
import ConfirmationModal from '@components/Objects/StyledElements/ConfirmationModal/ConfirmationModal';
|
||||||
import toast from 'react-hot-toast';
|
import toast from 'react-hot-toast';
|
||||||
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@components/Ui/select"
|
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@components/ui/select"
|
||||||
import { Button } from "@components/Ui/button"
|
import { Button } from "@components/ui/button"
|
||||||
import { Input } from "@components/Ui/input"
|
import { Input } from "@components/ui/input"
|
||||||
import { Textarea } from "@components/Ui/textarea"
|
import { Textarea } from "@components/ui/textarea"
|
||||||
import { Formik, Form, Field, ErrorMessage } from 'formik';
|
import { Formik, Form, Field, ErrorMessage } from 'formik';
|
||||||
import * as Yup from 'yup';
|
import * as Yup from 'yup';
|
||||||
import { Label } from '@components/Ui/label';
|
import { Label } from '@components/ui/label';
|
||||||
import { Badge } from '@components/Ui/badge';
|
import { Badge } from '@components/ui/badge';
|
||||||
import { getPaymentConfigs } from '@services/payments/payments';
|
import { getPaymentConfigs } from '@services/payments/payments';
|
||||||
import ProductLinkedCourses from './SubComponents/ProductLinkedCourses';
|
import ProductLinkedCourses from './SubComponents/ProductLinkedCourses';
|
||||||
import { usePaymentsEnabled } from '@hooks/usePaymentsEnabled';
|
import { usePaymentsEnabled } from '@hooks/usePaymentsEnabled';
|
||||||
|
|
|
||||||
|
|
@ -6,11 +6,11 @@ import { Formik, Form, Field, ErrorMessage } from 'formik';
|
||||||
import * as Yup from 'yup';
|
import * as Yup from 'yup';
|
||||||
import toast from 'react-hot-toast';
|
import toast from 'react-hot-toast';
|
||||||
import { mutate } from 'swr';
|
import { mutate } from 'swr';
|
||||||
import { Button } from "@components/Ui/button";
|
import { Button } from "@components/ui/button";
|
||||||
import { Input } from "@components/Ui/input";
|
import { Input } from "@components/ui/input";
|
||||||
import { Textarea } from "@components/Ui/textarea";
|
import { Textarea } from "@components/ui/textarea";
|
||||||
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@components/Ui/select";
|
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@components/ui/select";
|
||||||
import { Label } from "@components/Ui/label";
|
import { Label } from "@components/ui/label";
|
||||||
import currencyCodes from 'currency-codes';
|
import currencyCodes from 'currency-codes';
|
||||||
|
|
||||||
const validationSchema = Yup.object().shape({
|
const validationSchema = Yup.object().shape({
|
||||||
|
|
|
||||||
|
|
@ -2,8 +2,8 @@ import React, { useState } from 'react';
|
||||||
import { useOrg } from '@components/Contexts/OrgContext';
|
import { useOrg } from '@components/Contexts/OrgContext';
|
||||||
import { useLHSession } from '@components/Contexts/LHSessionContext';
|
import { useLHSession } from '@components/Contexts/LHSessionContext';
|
||||||
import { linkCourseToProduct } from '@services/payments/products';
|
import { linkCourseToProduct } from '@services/payments/products';
|
||||||
import { Button } from "@components/Ui/button";
|
import { Button } from "@components/ui/button";
|
||||||
import { Input } from "@components/Ui/input";
|
import { Input } from "@components/ui/input";
|
||||||
import { Search } from 'lucide-react';
|
import { Search } from 'lucide-react';
|
||||||
import toast from 'react-hot-toast';
|
import toast from 'react-hot-toast';
|
||||||
import { mutate } from 'swr';
|
import { mutate } from 'swr';
|
||||||
|
|
|
||||||
|
|
@ -3,7 +3,7 @@ import { getCoursesLinkedToProduct, unlinkCourseFromProduct } from '@services/pa
|
||||||
import { useLHSession } from '@components/Contexts/LHSessionContext';
|
import { useLHSession } from '@components/Contexts/LHSessionContext';
|
||||||
import { useOrg } from '@components/Contexts/OrgContext';
|
import { useOrg } from '@components/Contexts/OrgContext';
|
||||||
import { Trash2, Plus, BookOpen } from 'lucide-react';
|
import { Trash2, Plus, BookOpen } from 'lucide-react';
|
||||||
import { Button } from "@components/Ui/button";
|
import { Button } from "@components/ui/button";
|
||||||
import toast from 'react-hot-toast';
|
import toast from 'react-hot-toast';
|
||||||
import { mutate } from 'swr';
|
import { mutate } from 'swr';
|
||||||
import Modal from '@components/Objects/StyledElements/Modal/Modal';
|
import Modal from '@components/Objects/StyledElements/Modal/Modal';
|
||||||
|
|
|
||||||
36
apps/web/components/Hooks/useFeatureFlag.tsx
Normal file
36
apps/web/components/Hooks/useFeatureFlag.tsx
Normal file
|
|
@ -0,0 +1,36 @@
|
||||||
|
import { useOrg } from '@components/Contexts/OrgContext'
|
||||||
|
import { useEffect, useState } from 'react'
|
||||||
|
|
||||||
|
type FeatureType = {
|
||||||
|
path: string[]
|
||||||
|
defaultValue?: boolean
|
||||||
|
}
|
||||||
|
|
||||||
|
function useFeatureFlag(feature: FeatureType) {
|
||||||
|
const org = useOrg() as any
|
||||||
|
const [isEnabled, setIsEnabled] = useState<boolean>(!!feature.defaultValue)
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (org?.config?.config) {
|
||||||
|
let currentValue = org.config.config
|
||||||
|
|
||||||
|
// Traverse the path to get the feature flag value
|
||||||
|
for (const key of feature.path) {
|
||||||
|
if (currentValue && typeof currentValue === 'object') {
|
||||||
|
currentValue = currentValue[key]
|
||||||
|
} else {
|
||||||
|
currentValue = feature.defaultValue || false
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
setIsEnabled(!!currentValue)
|
||||||
|
} else {
|
||||||
|
setIsEnabled(!!feature.defaultValue)
|
||||||
|
}
|
||||||
|
}, [org, feature])
|
||||||
|
|
||||||
|
return isEnabled
|
||||||
|
}
|
||||||
|
|
||||||
|
export default useFeatureFlag
|
||||||
|
|
@ -4,8 +4,8 @@ import { useLHSession } from '@components/Contexts/LHSessionContext'
|
||||||
import useSWR from 'swr'
|
import useSWR from 'swr'
|
||||||
import { getProductsByCourse, getStripeProductCheckoutSession } from '@services/payments/products'
|
import { getProductsByCourse, getStripeProductCheckoutSession } from '@services/payments/products'
|
||||||
import { RefreshCcw, SquareCheck, ChevronDown, ChevronUp } from 'lucide-react'
|
import { RefreshCcw, SquareCheck, ChevronDown, ChevronUp } from 'lucide-react'
|
||||||
import { Badge } from '@components/Ui/badge'
|
import { Badge } from '@components/ui/badge'
|
||||||
import { Button } from '@components/Ui/button'
|
import { Button } from '@components/ui/button'
|
||||||
import toast from 'react-hot-toast'
|
import toast from 'react-hot-toast'
|
||||||
import { useRouter } from 'next/navigation'
|
import { useRouter } from 'next/navigation'
|
||||||
import { getUriWithOrg } from '@services/config/config'
|
import { getUriWithOrg } from '@services/config/config'
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,7 @@
|
||||||
'use client'
|
'use client'
|
||||||
import { Input } from "@components/Ui/input"
|
import { Input } from "@components/ui/input"
|
||||||
import { Textarea } from "@components/Ui/textarea"
|
import { Textarea } from "@components/ui/textarea"
|
||||||
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@components/Ui/select"
|
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@components/ui/select"
|
||||||
import FormLayout, {
|
import FormLayout, {
|
||||||
FormField,
|
FormField,
|
||||||
FormLabelAndMessage,
|
FormLabelAndMessage,
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,7 @@
|
||||||
'use client'
|
'use client'
|
||||||
|
|
||||||
import React from 'react'
|
import React from 'react'
|
||||||
import { Dialog, DialogContent, DialogDescription, DialogHeader, DialogTitle, DialogTrigger, DialogFooter } from "@components/Ui/dialog"
|
import { Dialog, DialogContent, DialogDescription, DialogHeader, DialogTitle, DialogTrigger, DialogFooter } from "@components/ui/dialog"
|
||||||
import { ButtonBlack } from '../Form/Form'
|
import { ButtonBlack } from '../Form/Form'
|
||||||
import { cn } from "@/lib/utils"
|
import { cn } from "@/lib/utils"
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -17,7 +17,7 @@ import {
|
||||||
DropdownMenuContent,
|
DropdownMenuContent,
|
||||||
DropdownMenuItem,
|
DropdownMenuItem,
|
||||||
DropdownMenuTrigger,
|
DropdownMenuTrigger,
|
||||||
} from "@components/Ui/dropdown-menu"
|
} from "@components/ui/dropdown-menu"
|
||||||
|
|
||||||
type Course = {
|
type Course = {
|
||||||
course_uuid: string
|
course_uuid: string
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
import { Settings, ChevronRight, CreditCard } from 'lucide-react'
|
import { Settings, ChevronRight, CreditCard } from 'lucide-react'
|
||||||
import { Alert, AlertTitle, AlertDescription } from '@components/Ui/alert'
|
import { Alert, AlertTitle, AlertDescription } from '@components/ui/alert'
|
||||||
import { AlertTriangle, ShoppingCart, Users } from 'lucide-react'
|
import { AlertTriangle, ShoppingCart, Users } from 'lucide-react'
|
||||||
import React from 'react'
|
import React from 'react'
|
||||||
import Link from 'next/link'
|
import Link from 'next/link'
|
||||||
|
|
|
||||||
|
|
@ -5,7 +5,7 @@ import * as ToggleGroupPrimitive from "@radix-ui/react-toggle-group"
|
||||||
import { type VariantProps } from "class-variance-authority"
|
import { type VariantProps } from "class-variance-authority"
|
||||||
|
|
||||||
import { cn } from "@/lib/utils"
|
import { cn } from "@/lib/utils"
|
||||||
import { toggleVariants } from "@components/Ui/toggle"
|
import { toggleVariants } from "@components/ui/toggle"
|
||||||
|
|
||||||
const ToggleGroupContext = React.createContext<
|
const ToggleGroupContext = React.createContext<
|
||||||
VariantProps<typeof toggleVariants>
|
VariantProps<typeof toggleVariants>
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue