mirror of
https://github.com/rzmk/learnhouse.git
synced 2025-12-19 04:19:25 +00:00
feat: UI improvements & migration script
This commit is contained in:
parent
e464b30147
commit
6cd1cf7e9c
5 changed files with 154 additions and 63 deletions
90
apps/api/migrations/versions/0314ec7791e1_payments.py
Normal file
90
apps/api/migrations/versions/0314ec7791e1_payments.py
Normal file
|
|
@ -0,0 +1,90 @@
|
|||
"""Payments
|
||||
|
||||
Revision ID: 0314ec7791e1
|
||||
Revises: 040ccb1d456e
|
||||
Create Date: 2024-11-23 19:41:14.064680
|
||||
|
||||
"""
|
||||
from typing import Sequence, Union
|
||||
|
||||
from alembic import op
|
||||
import sqlalchemy as sa # noqa: F401
|
||||
import sqlmodel # noqa: F401
|
||||
from sqlalchemy.dialects import postgresql
|
||||
|
||||
# revision identifiers, used by Alembic.
|
||||
revision: str = '0314ec7791e1'
|
||||
down_revision: Union[str, None] = '040ccb1d456e'
|
||||
branch_labels: Union[str, Sequence[str], None] = None
|
||||
depends_on: Union[str, Sequence[str], None] = None
|
||||
|
||||
|
||||
def upgrade() -> None:
|
||||
# ### commands auto generated by Alembic - please adjust! ###
|
||||
op.create_table('paymentsconfig',
|
||||
sa.Column('enabled', sa.Boolean(), nullable=False),
|
||||
sa.Column('active', sa.Boolean(), nullable=False),
|
||||
sa.Column('provider', postgresql.ENUM('STRIPE', name='paymentproviderenum', create_type=False), nullable=False),
|
||||
sa.Column('provider_specific_id', sqlmodel.sql.sqltypes.AutoString(), nullable=True),
|
||||
sa.Column('provider_config', sa.JSON(), nullable=True),
|
||||
sa.Column('id', sa.Integer(), nullable=False),
|
||||
sa.Column('org_id', sa.BigInteger(), nullable=True),
|
||||
sa.Column('creation_date', sa.DateTime(), nullable=False),
|
||||
sa.Column('update_date', sa.DateTime(), nullable=False),
|
||||
sa.ForeignKeyConstraint(['org_id'], ['organization.id'], ondelete='CASCADE'),
|
||||
sa.PrimaryKeyConstraint('id')
|
||||
)
|
||||
op.create_table('paymentsproduct',
|
||||
sa.Column('name', sqlmodel.sql.sqltypes.AutoString(), nullable=False),
|
||||
sa.Column('description', sqlmodel.sql.sqltypes.AutoString(), nullable=True),
|
||||
sa.Column('product_type', postgresql.ENUM('SUBSCRIPTION', 'ONE_TIME', name='paymentproducttypeenum', create_type=False), nullable=False),
|
||||
sa.Column('price_type', postgresql.ENUM('CUSTOMER_CHOICE', 'FIXED_PRICE', name='paymentpricetypeenum', create_type=False), nullable=False),
|
||||
sa.Column('benefits', sqlmodel.sql.sqltypes.AutoString(), nullable=False),
|
||||
sa.Column('amount', sa.Float(), nullable=False),
|
||||
sa.Column('currency', sqlmodel.sql.sqltypes.AutoString(), nullable=False),
|
||||
sa.Column('id', sa.Integer(), nullable=False),
|
||||
sa.Column('org_id', sa.BigInteger(), nullable=True),
|
||||
sa.Column('payments_config_id', sa.BigInteger(), nullable=True),
|
||||
sa.Column('provider_product_id', sa.String(), nullable=True),
|
||||
sa.Column('creation_date', sa.DateTime(), nullable=False),
|
||||
sa.Column('update_date', sa.DateTime(), nullable=False),
|
||||
sa.ForeignKeyConstraint(['org_id'], ['organization.id'], ondelete='CASCADE'),
|
||||
sa.ForeignKeyConstraint(['payments_config_id'], ['paymentsconfig.id'], ondelete='CASCADE'),
|
||||
sa.PrimaryKeyConstraint('id')
|
||||
)
|
||||
op.create_table('paymentscourse',
|
||||
sa.Column('course_id', sa.BigInteger(), nullable=True),
|
||||
sa.Column('id', sa.Integer(), nullable=False),
|
||||
sa.Column('payment_product_id', sa.BigInteger(), nullable=True),
|
||||
sa.Column('org_id', sa.BigInteger(), nullable=True),
|
||||
sa.Column('creation_date', sa.DateTime(), nullable=False),
|
||||
sa.Column('update_date', sa.DateTime(), nullable=False),
|
||||
sa.ForeignKeyConstraint(['course_id'], ['course.id'], ondelete='CASCADE'),
|
||||
sa.ForeignKeyConstraint(['org_id'], ['organization.id'], ondelete='CASCADE'),
|
||||
sa.ForeignKeyConstraint(['payment_product_id'], ['paymentsproduct.id'], ondelete='CASCADE'),
|
||||
sa.PrimaryKeyConstraint('id')
|
||||
)
|
||||
op.create_table('paymentsuser',
|
||||
sa.Column('status', postgresql.ENUM('PENDING', 'COMPLETED', 'ACTIVE', 'CANCELLED', 'FAILED', 'REFUNDED', name='paymentstatusenum', create_type=False), nullable=False),
|
||||
sa.Column('provider_specific_data', sa.JSON(), nullable=True),
|
||||
sa.Column('id', sa.Integer(), nullable=False),
|
||||
sa.Column('user_id', sa.BigInteger(), nullable=True),
|
||||
sa.Column('org_id', sa.BigInteger(), nullable=True),
|
||||
sa.Column('payment_product_id', sa.BigInteger(), nullable=True),
|
||||
sa.Column('creation_date', sa.DateTime(), nullable=False),
|
||||
sa.Column('update_date', sa.DateTime(), nullable=False),
|
||||
sa.ForeignKeyConstraint(['org_id'], ['organization.id'], ondelete='CASCADE'),
|
||||
sa.ForeignKeyConstraint(['payment_product_id'], ['paymentsproduct.id'], ondelete='CASCADE'),
|
||||
sa.ForeignKeyConstraint(['user_id'], ['user.id'], ondelete='CASCADE'),
|
||||
sa.PrimaryKeyConstraint('id')
|
||||
)
|
||||
# ### end Alembic commands ###
|
||||
|
||||
|
||||
def downgrade() -> None:
|
||||
# ### commands auto generated by Alembic - please adjust! ###
|
||||
op.drop_table('paymentsuser')
|
||||
op.drop_table('paymentscourse')
|
||||
op.drop_table('paymentsproduct')
|
||||
op.drop_table('paymentsconfig')
|
||||
# ### end Alembic commands ###
|
||||
|
|
@ -26,7 +26,7 @@ import toast from 'react-hot-toast'
|
|||
import { mutate } from 'swr'
|
||||
import ConfirmationModal from '@components/StyledElements/ConfirmationModal/ConfirmationModal'
|
||||
import { useMediaQuery } from 'usehooks-ts'
|
||||
import PaidCourseActivity from '@components/Objects/Courses/CourseActions/PaidCourseActivity'
|
||||
import PaidCourseActivityDisclaimer from '@components/Objects/Courses/CourseActions/PaidCourseActivityDisclaimer'
|
||||
|
||||
interface ActivityClientProps {
|
||||
activityid: string
|
||||
|
|
@ -81,7 +81,6 @@ function ActivityClient(props: ActivityClientProps) {
|
|||
else {
|
||||
setBgColor('bg-zinc-950');
|
||||
}
|
||||
console.log(activity.content)
|
||||
}
|
||||
, [activity, pathname])
|
||||
|
||||
|
|
@ -175,19 +174,16 @@ function ActivityClient(props: ActivityClientProps) {
|
|||
)}
|
||||
|
||||
{activity && activity.published == true && (
|
||||
<div
|
||||
className={`p-7 drop-shadow-sm rounded-lg ${bgColor}`}
|
||||
>
|
||||
{/* Paid Courses */}
|
||||
{activity.content.paid_access == false && (
|
||||
<PaidCourseActivity course={course} />
|
||||
)}
|
||||
<>
|
||||
{activity.content.paid_access == false ? (
|
||||
<PaidCourseActivityDisclaimer course={course} />
|
||||
) : (
|
||||
<div className={`p-7 drop-shadow-sm rounded-lg ${bgColor}`}>
|
||||
{/* Activity Types */}
|
||||
<div>
|
||||
{activity.activity_type == 'TYPE_DYNAMIC' && (
|
||||
<Canva content={activity.content} activity={activity} />
|
||||
)}
|
||||
{/* todo : use apis & streams instead of this */}
|
||||
{activity.activity_type == 'TYPE_VIDEO' && (
|
||||
<VideoActivity course={course} activity={activity} />
|
||||
)}
|
||||
|
|
@ -215,6 +211,8 @@ function ActivityClient(props: ActivityClientProps) {
|
|||
</div>
|
||||
</div>
|
||||
)}
|
||||
</>
|
||||
)}
|
||||
{<div style={{ height: '100px' }}></div>}
|
||||
</div>
|
||||
</GeneralWrapperStyled>
|
||||
|
|
|
|||
|
|
@ -7,6 +7,7 @@ import {
|
|||
Eye,
|
||||
File,
|
||||
FilePenLine,
|
||||
FileSymlink,
|
||||
Globe,
|
||||
Lock,
|
||||
MoreVertical,
|
||||
|
|
@ -27,6 +28,7 @@ import { useOrg } from '@components/Contexts/OrgContext'
|
|||
import { useCourse } from '@components/Contexts/CourseContext'
|
||||
import toast from 'react-hot-toast'
|
||||
import { useMediaQuery } from 'usehooks-ts'
|
||||
import ToolTip from '@components/StyledElements/Tooltip/Tooltip'
|
||||
|
||||
type ActivitiyElementProps = {
|
||||
orgslug: string
|
||||
|
|
@ -176,6 +178,8 @@ function ActivityElement(props: ActivitiyElementProps) {
|
|||
)}
|
||||
<span>{!props.activity.published ? 'Publish' : 'Unpublish'}</span>
|
||||
</button>
|
||||
<div className="w-px h-3 bg-gray-300 mx-1 self-center rounded-full hidden sm:block" />
|
||||
<ToolTip content="Preview Activity" sideOffset={8}>
|
||||
<Link
|
||||
href={
|
||||
getUriWithOrg(props.orgslug, '') +
|
||||
|
|
@ -191,9 +195,9 @@ function ActivityElement(props: ActivitiyElementProps) {
|
|||
className="p-1 px-2 sm:px-3 bg-gradient-to-bl text-cyan-800 from-sky-400/50 to-cyan-200/80 border border-cyan-600/10 shadow-md rounded-md font-bold text-xs flex items-center space-x-1 transition-colors duration-200 hover:from-sky-500/50 hover:to-cyan-300/80"
|
||||
rel="noopener noreferrer"
|
||||
>
|
||||
<Eye strokeWidth={2} size={12} className="text-sky-600" />
|
||||
<span>Preview</span>
|
||||
<Eye strokeWidth={2} size={14} className="text-sky-600" />
|
||||
</Link>
|
||||
</ToolTip>
|
||||
{/* Delete Button */}
|
||||
<ConfirmationModal
|
||||
confirmationMessage="Are you sure you want to delete this activity ?"
|
||||
|
|
@ -205,7 +209,6 @@ function ActivityElement(props: ActivitiyElementProps) {
|
|||
rel="noopener noreferrer"
|
||||
>
|
||||
<X size={15} className="text-rose-200 font-bold" />
|
||||
{!isMobile && <span className="text-rose-200 font-bold text-xs">Delete</span>}
|
||||
</button>
|
||||
}
|
||||
functionToExecute={() => deleteActivityUI()}
|
||||
|
|
|
|||
|
|
@ -74,7 +74,7 @@ function CoursePaidOptions({ course }: CoursePaidOptionsProps) {
|
|||
<div key={product.id} className="bg-slate-50/30 p-4 rounded-lg nice-shadow flex flex-col">
|
||||
<div className="flex justify-between items-start mb-2">
|
||||
<div className="flex flex-col space-y-1 items-start">
|
||||
<Badge className='w-fit flex items-center space-x-2' variant="outline">
|
||||
<Badge className='w-fit flex items-center space-x-2 bg-gray-100/50' variant="outline">
|
||||
{product.product_type === 'subscription' ? <RefreshCcw size={12} /> : <SquareCheck size={12} />}
|
||||
<span className='text-sm'>
|
||||
{product.product_type === 'subscription' ? 'Subscription' : 'One-time payment'}
|
||||
|
|
|
|||
|
|
@ -6,16 +6,16 @@ interface PaidCourseActivityProps {
|
|||
course: any;
|
||||
}
|
||||
|
||||
function PaidCourseActivity({ course }: PaidCourseActivityProps) {
|
||||
function PaidCourseActivityDisclaimer({ course }: PaidCourseActivityProps) {
|
||||
return (
|
||||
<div className="space-y-4 ">
|
||||
<div className="p-4 bg-amber-50 border border-amber-200 rounded-lg nice-shadow">
|
||||
<div className="space-y-4 max-w-lg mx-auto">
|
||||
<div className="p-4 bg-amber-50 border border-amber-200 rounded-lg ">
|
||||
<div className="flex items-center gap-3">
|
||||
<AlertCircle className="w-5 h-5 text-amber-800" />
|
||||
<h3 className="text-amber-800 font-semibold">Paid Content</h3>
|
||||
</div>
|
||||
<p className="text-amber-700 text-sm mt-1">
|
||||
This content requires a course purchase to access. Please purchase the course to continue.
|
||||
This content requires a course purchase to access.
|
||||
</p>
|
||||
</div>
|
||||
<CoursePaidOptions course={course} />
|
||||
|
|
@ -23,4 +23,4 @@ function PaidCourseActivity({ course }: PaidCourseActivityProps) {
|
|||
)
|
||||
}
|
||||
|
||||
export default PaidCourseActivity
|
||||
export default PaidCourseActivityDisclaimer
|
||||
Loading…
Add table
Add a link
Reference in a new issue