Add user account page
This commit is contained in:
18
apps/builder/pages/account.tsx
Normal file
18
apps/builder/pages/account.tsx
Normal file
@ -0,0 +1,18 @@
|
||||
import { Stack } from '@chakra-ui/react'
|
||||
import { AccountHeader } from 'components/account/AccountHeader'
|
||||
import { Seo } from 'components/Seo'
|
||||
import { UserContext } from 'contexts/UserContext'
|
||||
import { AccountContent } from 'layouts/account/AccountContent'
|
||||
|
||||
const AccountSubscriptionPage = () => {
|
||||
return (
|
||||
<UserContext>
|
||||
<Seo title="My account" />{' '}
|
||||
<Stack>
|
||||
<AccountHeader />
|
||||
<AccountContent />
|
||||
</Stack>
|
||||
</UserContext>
|
||||
)
|
||||
}
|
||||
export default AccountSubscriptionPage
|
@ -1,4 +1,4 @@
|
||||
import NextAuth from 'next-auth'
|
||||
import NextAuth, { NextAuthOptions } from 'next-auth'
|
||||
import { PrismaAdapter } from '@next-auth/prisma-adapter'
|
||||
import EmailProvider from 'next-auth/providers/email'
|
||||
import GitHubProvider from 'next-auth/providers/github'
|
||||
@ -8,6 +8,7 @@ import CredentialsProvider from 'next-auth/providers/credentials'
|
||||
import prisma from 'libs/prisma'
|
||||
import { Provider } from 'next-auth/providers'
|
||||
import { User } from 'db'
|
||||
import { NextApiRequest, NextApiResponse } from 'next'
|
||||
|
||||
const providers: Provider[] = [
|
||||
EmailProvider({
|
||||
@ -67,7 +68,7 @@ if (process.env.NODE_ENV !== 'production')
|
||||
})
|
||||
)
|
||||
|
||||
export default NextAuth({
|
||||
const createOptions = (req: NextApiRequest): NextAuthOptions => ({
|
||||
adapter: PrismaAdapter(prisma),
|
||||
secret: process.env.SECRET,
|
||||
providers,
|
||||
@ -75,13 +76,25 @@ export default NextAuth({
|
||||
strategy: process.env.NODE_ENV === 'production' ? 'database' : 'jwt',
|
||||
},
|
||||
callbacks: {
|
||||
jwt: async ({ token, user }) => {
|
||||
user && (token.user = user)
|
||||
jwt: async ({ token, user, account }) => {
|
||||
if (req.url === '/api/auth/session?update' && token.user) {
|
||||
token.user = await prisma.user.findUnique({
|
||||
where: { id: (token.user as User).id },
|
||||
})
|
||||
} else if (user) {
|
||||
token.user = user
|
||||
}
|
||||
account?.type && (token.providerType = account?.type)
|
||||
return token
|
||||
},
|
||||
session: async ({ session, token, user }) => {
|
||||
token?.user ? (session.user = token.user as User) : (session.user = user)
|
||||
return session
|
||||
return { ...session, providerType: token.providerType }
|
||||
},
|
||||
},
|
||||
})
|
||||
|
||||
const handler = (req: NextApiRequest, res: NextApiResponse) => {
|
||||
NextAuth(req, res, createOptions(req))
|
||||
}
|
||||
export default handler
|
||||
|
46
apps/builder/pages/api/storage/upload-url.ts
Normal file
46
apps/builder/pages/api/storage/upload-url.ts
Normal file
@ -0,0 +1,46 @@
|
||||
import aws from 'aws-sdk'
|
||||
import { NextApiRequest, NextApiResponse } from 'next'
|
||||
import { getSession } from 'next-auth/react'
|
||||
import { methodNotAllowed } from 'services/api/utils'
|
||||
|
||||
const maxUploadFileSize = 10485760 // 10 MB
|
||||
const handler = async (
|
||||
req: NextApiRequest,
|
||||
res: NextApiResponse
|
||||
): Promise<void> => {
|
||||
try {
|
||||
res.setHeader('Access-Control-Allow-Origin', '*')
|
||||
if (req.method === 'GET') {
|
||||
const session = await getSession({ req })
|
||||
if (!session) {
|
||||
res.status(401)
|
||||
return
|
||||
}
|
||||
aws.config.update({
|
||||
accessKeyId: process.env.S3_UPLOAD_KEY,
|
||||
secretAccessKey: process.env.S3_UPLOAD_SECRET,
|
||||
region: process.env.S3_UPLOAD_REGION,
|
||||
signatureVersion: 'v4',
|
||||
})
|
||||
|
||||
const s3 = new aws.S3()
|
||||
const post = s3.createPresignedPost({
|
||||
Bucket: process.env.S3_UPLOAD_BUCKET,
|
||||
Fields: {
|
||||
ACL: 'public-read',
|
||||
key: req.query.key,
|
||||
'Content-Type': req.query.fileType,
|
||||
},
|
||||
Expires: 120, // seconds
|
||||
Conditions: [['content-length-range', 0, maxUploadFileSize]],
|
||||
})
|
||||
|
||||
return res.status(200).json(post)
|
||||
}
|
||||
return methodNotAllowed(res)
|
||||
} catch (err) {
|
||||
console.log(err)
|
||||
}
|
||||
}
|
||||
|
||||
export default handler
|
35
apps/builder/pages/api/stripe/checkout.ts
Normal file
35
apps/builder/pages/api/stripe/checkout.ts
Normal file
@ -0,0 +1,35 @@
|
||||
import { NextApiRequest, NextApiResponse } from 'next'
|
||||
import { methodNotAllowed } from 'services/api/utils'
|
||||
import Stripe from 'stripe'
|
||||
|
||||
const usdPriceIdTest = 'price_1Jc4TQKexUFvKTWyGvsH4Ff5'
|
||||
const createCheckoutSession = async (
|
||||
req: NextApiRequest,
|
||||
res: NextApiResponse
|
||||
) => {
|
||||
if (req.method === 'POST') {
|
||||
if (!process.env.STRIPE_SECRET_KEY)
|
||||
throw Error('STRIPE_SECRET_KEY var is missing')
|
||||
const stripe = new Stripe(process.env.STRIPE_SECRET_KEY, {
|
||||
apiVersion: '2020-08-27',
|
||||
})
|
||||
const { email } = req.body
|
||||
const session = await stripe.checkout.sessions.create({
|
||||
success_url: `${req.headers.origin}/typebots?stripe=success`,
|
||||
cancel_url: `${req.headers.origin}/typebots?stripe=cancel`,
|
||||
automatic_tax: { enabled: true },
|
||||
allow_promotion_codes: true,
|
||||
customer_email: email,
|
||||
line_items: [
|
||||
{
|
||||
price: usdPriceIdTest,
|
||||
quantity: 1,
|
||||
},
|
||||
],
|
||||
})
|
||||
res.status(201).json(session)
|
||||
}
|
||||
return methodNotAllowed(res)
|
||||
}
|
||||
|
||||
export default createCheckoutSession
|
73
apps/builder/pages/api/stripe/webhook.ts
Normal file
73
apps/builder/pages/api/stripe/webhook.ts
Normal file
@ -0,0 +1,73 @@
|
||||
import { NextApiRequest, NextApiResponse } from 'next'
|
||||
import { methodNotAllowed } from 'services/api/utils'
|
||||
import Stripe from 'stripe'
|
||||
import Cors from 'micro-cors'
|
||||
import { buffer } from 'micro'
|
||||
import prisma from 'libs/prisma'
|
||||
import { Plan } from 'db'
|
||||
|
||||
if (!process.env.STRIPE_SECRET_KEY || !process.env.STRIPE_WEBHOOK_SECRET)
|
||||
throw new Error('STRIPE_SECRET_KEY or STRIPE_WEBHOOK_SECRET missing')
|
||||
const stripe = new Stripe(process.env.STRIPE_SECRET_KEY, {
|
||||
apiVersion: '2020-08-27',
|
||||
})
|
||||
|
||||
const cors = Cors({
|
||||
allowMethods: ['POST', 'HEAD'],
|
||||
})
|
||||
|
||||
const webhookSecret = process.env.STRIPE_WEBHOOK_SECRET as string
|
||||
|
||||
export const config = {
|
||||
api: {
|
||||
bodyParser: false,
|
||||
},
|
||||
}
|
||||
|
||||
const webhookHandler = async (req: NextApiRequest, res: NextApiResponse) => {
|
||||
if (req.method === 'POST') {
|
||||
const buf = await buffer(req)
|
||||
const sig = req.headers['stripe-signature']
|
||||
|
||||
if (!sig) return res.status(400).send(`stripe-signature is missing`)
|
||||
try {
|
||||
const event = stripe.webhooks.constructEvent(
|
||||
buf.toString(),
|
||||
sig.toString(),
|
||||
webhookSecret
|
||||
)
|
||||
switch (event.type) {
|
||||
case 'checkout.session.completed': {
|
||||
const session = event.data.object as Stripe.Checkout.Session
|
||||
const { customer_email } = session
|
||||
if (!customer_email)
|
||||
return res.status(500).send(`customer_email not found`)
|
||||
await prisma.user.update({
|
||||
where: { email: customer_email },
|
||||
data: { plan: Plan.PRO, stripeId: session.customer as string },
|
||||
})
|
||||
}
|
||||
case 'customer.subscription.deleted': {
|
||||
const subscription = event.data.object as Stripe.Subscription
|
||||
await prisma.user.update({
|
||||
where: {
|
||||
stripeId: subscription.customer as string,
|
||||
},
|
||||
data: {
|
||||
plan: Plan.FREE,
|
||||
},
|
||||
})
|
||||
}
|
||||
}
|
||||
} catch (err) {
|
||||
if (err instanceof Error) {
|
||||
console.error(err)
|
||||
return res.status(400).send(`Webhook Error: ${err.message}`)
|
||||
}
|
||||
}
|
||||
}
|
||||
return methodNotAllowed(res)
|
||||
}
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
export default cors(webhookHandler as any)
|
24
apps/builder/pages/api/users/[id].ts
Normal file
24
apps/builder/pages/api/users/[id].ts
Normal file
@ -0,0 +1,24 @@
|
||||
import prisma from 'libs/prisma'
|
||||
import { NextApiRequest, NextApiResponse } from 'next'
|
||||
import { getSession } from 'next-auth/react'
|
||||
import { methodNotAllowed } from 'services/api/utils'
|
||||
|
||||
const handler = async (req: NextApiRequest, res: NextApiResponse) => {
|
||||
const session = await getSession({ req })
|
||||
|
||||
if (!session?.user)
|
||||
return res.status(401).json({ message: 'Not authenticated' })
|
||||
|
||||
const id = req.query.id.toString()
|
||||
if (req.method === 'PUT') {
|
||||
const data = JSON.parse(req.body)
|
||||
const typebots = await prisma.user.update({
|
||||
where: { id },
|
||||
data,
|
||||
})
|
||||
return res.send({ typebots })
|
||||
}
|
||||
return methodNotAllowed(res)
|
||||
}
|
||||
|
||||
export default handler
|
@ -1,18 +1,20 @@
|
||||
import withAuth from 'components/HOC/withUser'
|
||||
import React from 'react'
|
||||
import { Stack } from '@chakra-ui/layout'
|
||||
import { DashboardHeader } from 'components/dashboard/DashboardHeader'
|
||||
import { Seo } from 'components/Seo'
|
||||
import { FolderContent } from 'components/dashboard/FolderContent'
|
||||
import { UserContext } from 'contexts/UserContext'
|
||||
|
||||
const DashboardPage = () => {
|
||||
return (
|
||||
<Stack>
|
||||
<UserContext>
|
||||
<Seo title="My typebots" />
|
||||
<DashboardHeader />
|
||||
<FolderContent folder={null} />
|
||||
</Stack>
|
||||
<Stack>
|
||||
<DashboardHeader />
|
||||
<FolderContent folder={null} />
|
||||
</Stack>
|
||||
</UserContext>
|
||||
)
|
||||
}
|
||||
|
||||
export default withAuth(DashboardPage)
|
||||
export default DashboardPage
|
||||
|
@ -1,6 +1,5 @@
|
||||
import { Flex } from '@chakra-ui/layout'
|
||||
import { Board } from 'components/board/Board'
|
||||
import withAuth from 'components/HOC/withUser'
|
||||
import { Seo } from 'components/Seo'
|
||||
import { TypebotHeader } from 'components/shared/TypebotHeader'
|
||||
import { EditorContext } from 'contexts/EditorContext'
|
||||
@ -11,25 +10,28 @@ import { KBarProvider } from 'kbar'
|
||||
import React from 'react'
|
||||
import { actions } from 'libs/kbar'
|
||||
import { KBar } from 'components/shared/KBar'
|
||||
import { UserContext } from 'contexts/UserContext'
|
||||
|
||||
const TypebotEditPage = () => {
|
||||
const { query } = useRouter()
|
||||
return (
|
||||
<TypebotContext typebotId={query.id?.toString()}>
|
||||
<Seo title="Editor" />
|
||||
<EditorContext>
|
||||
<KBarProvider actions={actions}>
|
||||
<KBar />
|
||||
<Flex overflow="hidden" h="100vh" flexDir="column">
|
||||
<TypebotHeader />
|
||||
<GraphProvider>
|
||||
<Board />
|
||||
</GraphProvider>
|
||||
</Flex>
|
||||
</KBarProvider>
|
||||
</EditorContext>
|
||||
</TypebotContext>
|
||||
<UserContext>
|
||||
<TypebotContext typebotId={query.id?.toString()}>
|
||||
<Seo title="Editor" />
|
||||
<EditorContext>
|
||||
<KBarProvider actions={actions}>
|
||||
<KBar />
|
||||
<Flex overflow="hidden" h="100vh" flexDir="column">
|
||||
<TypebotHeader />
|
||||
<GraphProvider>
|
||||
<Board />
|
||||
</GraphProvider>
|
||||
</Flex>
|
||||
</KBarProvider>
|
||||
</EditorContext>
|
||||
</TypebotContext>
|
||||
</UserContext>
|
||||
)
|
||||
}
|
||||
|
||||
export default withAuth(TypebotEditPage)
|
||||
export default TypebotEditPage
|
||||
|
@ -1,23 +1,25 @@
|
||||
import { Flex } from '@chakra-ui/layout'
|
||||
import withAuth from 'components/HOC/withUser'
|
||||
import { ResultsContent } from 'layouts/results/ResultsContent'
|
||||
import { Seo } from 'components/Seo'
|
||||
import { TypebotHeader } from 'components/shared/TypebotHeader'
|
||||
import { TypebotContext } from 'contexts/TypebotContext'
|
||||
import { useRouter } from 'next/router'
|
||||
import React from 'react'
|
||||
import { UserContext } from 'contexts/UserContext'
|
||||
|
||||
const ResultsPage = () => {
|
||||
const { query } = useRouter()
|
||||
return (
|
||||
<TypebotContext typebotId={query.id?.toString()}>
|
||||
<Seo title="Share" />
|
||||
<Flex overflow="hidden" h="100vh" flexDir="column">
|
||||
<TypebotHeader />
|
||||
<ResultsContent />
|
||||
</Flex>
|
||||
</TypebotContext>
|
||||
<UserContext>
|
||||
<TypebotContext typebotId={query.id?.toString()}>
|
||||
<Seo title="Share" />
|
||||
<Flex overflow="hidden" h="100vh" flexDir="column">
|
||||
<TypebotHeader />
|
||||
<ResultsContent />
|
||||
</Flex>
|
||||
</TypebotContext>
|
||||
</UserContext>
|
||||
)
|
||||
}
|
||||
|
||||
export default withAuth(ResultsPage)
|
||||
export default ResultsPage
|
||||
|
@ -1,23 +1,25 @@
|
||||
import { Flex } from '@chakra-ui/layout'
|
||||
import withAuth from 'components/HOC/withUser'
|
||||
import { ResultsContent } from 'layouts/results/ResultsContent'
|
||||
import { Seo } from 'components/Seo'
|
||||
import { TypebotHeader } from 'components/shared/TypebotHeader'
|
||||
import { TypebotContext } from 'contexts/TypebotContext'
|
||||
import { useRouter } from 'next/router'
|
||||
import React from 'react'
|
||||
import { UserContext } from 'contexts/UserContext'
|
||||
|
||||
const AnalyticsPage = () => {
|
||||
const { query } = useRouter()
|
||||
return (
|
||||
<TypebotContext typebotId={query.id?.toString()}>
|
||||
<Seo title="Analytics" />
|
||||
<Flex overflow="hidden" h="100vh" flexDir="column">
|
||||
<TypebotHeader />
|
||||
<ResultsContent />
|
||||
</Flex>
|
||||
</TypebotContext>
|
||||
<UserContext>
|
||||
<TypebotContext typebotId={query.id?.toString()}>
|
||||
<Seo title="Analytics" />
|
||||
<Flex overflow="hidden" h="100vh" flexDir="column">
|
||||
<TypebotHeader />
|
||||
<ResultsContent />
|
||||
</Flex>
|
||||
</TypebotContext>
|
||||
</UserContext>
|
||||
)
|
||||
}
|
||||
|
||||
export default withAuth(AnalyticsPage)
|
||||
export default AnalyticsPage
|
||||
|
@ -1,23 +1,25 @@
|
||||
import { Flex } from '@chakra-ui/layout'
|
||||
import withAuth from 'components/HOC/withUser'
|
||||
import { Seo } from 'components/Seo'
|
||||
import { SettingsContent } from 'components/settings/SettingsContent'
|
||||
import { TypebotHeader } from 'components/shared/TypebotHeader'
|
||||
import { TypebotContext } from 'contexts/TypebotContext'
|
||||
import { UserContext } from 'contexts/UserContext'
|
||||
import { useRouter } from 'next/router'
|
||||
import React from 'react'
|
||||
|
||||
const SettingsPage = () => {
|
||||
const { query } = useRouter()
|
||||
return (
|
||||
<TypebotContext typebotId={query.id?.toString()}>
|
||||
<Seo title="Settings" />
|
||||
<Flex overflow="hidden" h="100vh" flexDir="column">
|
||||
<TypebotHeader />
|
||||
<SettingsContent />
|
||||
</Flex>
|
||||
</TypebotContext>
|
||||
<UserContext>
|
||||
<TypebotContext typebotId={query.id?.toString()}>
|
||||
<Seo title="Settings" />
|
||||
<Flex overflow="hidden" h="100vh" flexDir="column">
|
||||
<TypebotHeader />
|
||||
<SettingsContent />
|
||||
</Flex>
|
||||
</TypebotContext>
|
||||
</UserContext>
|
||||
)
|
||||
}
|
||||
|
||||
export default withAuth(SettingsPage)
|
||||
export default SettingsPage
|
||||
|
@ -1,23 +1,25 @@
|
||||
import { Flex } from '@chakra-ui/layout'
|
||||
import withAuth from 'components/HOC/withUser'
|
||||
import { Seo } from 'components/Seo'
|
||||
import { ShareContent } from 'components/share/ShareContent'
|
||||
import { TypebotHeader } from 'components/shared/TypebotHeader'
|
||||
import { TypebotContext } from 'contexts/TypebotContext'
|
||||
import { UserContext } from 'contexts/UserContext'
|
||||
import { useRouter } from 'next/router'
|
||||
import React from 'react'
|
||||
|
||||
const SharePage = () => {
|
||||
const { query } = useRouter()
|
||||
return (
|
||||
<TypebotContext typebotId={query.id?.toString()}>
|
||||
<Seo title="Share" />
|
||||
<Flex overflow="hidden" h="100vh" flexDir="column">
|
||||
<TypebotHeader />
|
||||
<ShareContent />
|
||||
</Flex>
|
||||
</TypebotContext>
|
||||
<UserContext>
|
||||
<TypebotContext typebotId={query.id?.toString()}>
|
||||
<Seo title="Share" />
|
||||
<Flex overflow="hidden" h="100vh" flexDir="column">
|
||||
<TypebotHeader />
|
||||
<ShareContent />
|
||||
</Flex>
|
||||
</TypebotContext>
|
||||
</UserContext>
|
||||
)
|
||||
}
|
||||
|
||||
export default withAuth(SharePage)
|
||||
export default SharePage
|
||||
|
@ -1,23 +1,25 @@
|
||||
import { Flex } from '@chakra-ui/layout'
|
||||
import withAuth from 'components/HOC/withUser'
|
||||
import { Seo } from 'components/Seo'
|
||||
import { TypebotHeader } from 'components/shared/TypebotHeader'
|
||||
import { ThemeContent } from 'components/theme/ThemeContent'
|
||||
import { TypebotContext } from 'contexts/TypebotContext'
|
||||
import { UserContext } from 'contexts/UserContext'
|
||||
import { useRouter } from 'next/router'
|
||||
import React from 'react'
|
||||
|
||||
const ThemePage = () => {
|
||||
const { query } = useRouter()
|
||||
return (
|
||||
<TypebotContext typebotId={query.id?.toString()}>
|
||||
<Seo title="Theme" />
|
||||
<Flex overflow="hidden" h="100vh" flexDir="column">
|
||||
<TypebotHeader />
|
||||
<ThemeContent />
|
||||
</Flex>
|
||||
</TypebotContext>
|
||||
<UserContext>
|
||||
<TypebotContext typebotId={query.id?.toString()}>
|
||||
<Seo title="Theme" />
|
||||
<Flex overflow="hidden" h="100vh" flexDir="column">
|
||||
<TypebotHeader />
|
||||
<ThemeContent />
|
||||
</Flex>
|
||||
</TypebotContext>
|
||||
</UserContext>
|
||||
)
|
||||
}
|
||||
|
||||
export default withAuth(ThemePage)
|
||||
export default ThemePage
|
||||
|
@ -1,14 +1,13 @@
|
||||
import React, { useState } from 'react'
|
||||
import { Button, Stack, useToast } from '@chakra-ui/react'
|
||||
import { useUser } from 'services/user'
|
||||
import { useRouter } from 'next/router'
|
||||
import { Seo } from 'components/Seo'
|
||||
import { DashboardHeader } from 'components/dashboard/DashboardHeader'
|
||||
import { createTypebot } from 'services/typebots'
|
||||
import withAuth from 'components/HOC/withUser'
|
||||
import { UserContext, useUser } from 'contexts/UserContext'
|
||||
|
||||
const TemplatesPage = () => {
|
||||
const user = useUser()
|
||||
const { user } = useUser()
|
||||
const router = useRouter()
|
||||
|
||||
const [isLoading, setIsLoading] = useState(false)
|
||||
@ -31,14 +30,20 @@ const TemplatesPage = () => {
|
||||
}
|
||||
|
||||
return (
|
||||
<Stack>
|
||||
<UserContext>
|
||||
<Seo title="Templates" />
|
||||
<DashboardHeader />
|
||||
<Button ml={4} onClick={() => handleCreateSubmit()} isLoading={isLoading}>
|
||||
Start from scratch
|
||||
</Button>
|
||||
</Stack>
|
||||
<Stack>
|
||||
<DashboardHeader />
|
||||
<Button
|
||||
ml={4}
|
||||
onClick={() => handleCreateSubmit()}
|
||||
isLoading={isLoading}
|
||||
>
|
||||
Start from scratch
|
||||
</Button>
|
||||
</Stack>
|
||||
</UserContext>
|
||||
)
|
||||
}
|
||||
|
||||
export default withAuth(TemplatesPage)
|
||||
export default TemplatesPage
|
||||
|
@ -1,4 +1,3 @@
|
||||
import withAuth from 'components/HOC/withUser'
|
||||
import React from 'react'
|
||||
import { Flex, Stack } from '@chakra-ui/layout'
|
||||
import { DashboardHeader } from 'components/dashboard/DashboardHeader'
|
||||
@ -7,6 +6,7 @@ import { FolderContent } from 'components/dashboard/FolderContent'
|
||||
import { useRouter } from 'next/router'
|
||||
import { useFolderContent } from 'services/folders'
|
||||
import { Spinner, useToast } from '@chakra-ui/react'
|
||||
import { UserContext } from 'contexts/UserContext'
|
||||
|
||||
const FolderPage = () => {
|
||||
const router = useRouter()
|
||||
@ -27,18 +27,20 @@ const FolderPage = () => {
|
||||
})
|
||||
|
||||
return (
|
||||
<Stack>
|
||||
<UserContext>
|
||||
<Seo title="My typebots" />
|
||||
<DashboardHeader />
|
||||
{!folder ? (
|
||||
<Flex flex="1">
|
||||
<Spinner mx="auto" />
|
||||
</Flex>
|
||||
) : (
|
||||
<FolderContent folder={folder} />
|
||||
)}
|
||||
</Stack>
|
||||
<Stack>
|
||||
<DashboardHeader />
|
||||
{!folder ? (
|
||||
<Flex flex="1">
|
||||
<Spinner mx="auto" />
|
||||
</Flex>
|
||||
) : (
|
||||
<FolderContent folder={folder} />
|
||||
)}
|
||||
</Stack>
|
||||
</UserContext>
|
||||
)
|
||||
}
|
||||
|
||||
export default withAuth(FolderPage)
|
||||
export default FolderPage
|
||||
|
Reference in New Issue
Block a user