build cms ok,
This commit is contained in:
@@ -87,6 +87,7 @@ module.exports = {
|
|||||||
'**/*.bak',
|
'**/*.bak',
|
||||||
'**/*copy.*',
|
'**/*copy.*',
|
||||||
'**/*copy*.*',
|
'**/*copy*.*',
|
||||||
|
'**/*draft*/**',
|
||||||
//
|
//
|
||||||
],
|
],
|
||||||
overrides: [
|
overrides: [
|
||||||
|
5
002_source/cms/Dockerfile
Normal file
5
002_source/cms/Dockerfile
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
FROM 192.168.10.61:5000/nvm_ubuntu:latest
|
||||||
|
|
||||||
|
WORKDIR /app
|
||||||
|
|
||||||
|
RUN nvm install 22
|
@@ -3,5 +3,10 @@
|
|||||||
set -ex
|
set -ex
|
||||||
|
|
||||||
reset
|
reset
|
||||||
|
|
||||||
rm -rf .next
|
rm -rf .next
|
||||||
|
|
||||||
|
# pnpm run typecheck
|
||||||
|
# pnpm run lint:w
|
||||||
|
|
||||||
pnpm run build
|
pnpm run build
|
||||||
|
7
002_source/cms/scripts/docker/entrypoint.sh
Normal file → Executable file
7
002_source/cms/scripts/docker/entrypoint.sh
Normal file → Executable file
@@ -3,13 +3,16 @@
|
|||||||
# set -x
|
# set -x
|
||||||
|
|
||||||
# nvm use 20
|
# nvm use 20
|
||||||
|
nvm install 18
|
||||||
nvm alias default 18
|
nvm alias default 18
|
||||||
nvm use default
|
nvm use default
|
||||||
node -v
|
node -v
|
||||||
|
|
||||||
npm i -D
|
sleep infinity
|
||||||
|
|
||||||
npm run dev
|
# pnpm i -D
|
||||||
|
|
||||||
|
# pnpm run start
|
||||||
|
|
||||||
# while true; do
|
# while true; do
|
||||||
# if [ "$NODE_ENV" = "development" ]; then
|
# if [ "$NODE_ENV" = "development" ]; then
|
||||||
|
@@ -1,8 +1,26 @@
|
|||||||
// src/app/dashboard/students/page.tsx
|
// src/app/dashboard/students/page.tsx
|
||||||
|
|
||||||
'use client';
|
'use client';
|
||||||
|
|
||||||
import type { Student } from '@/db/Students/type.d';
|
import type { Student } from '@/db/Students/type.d';
|
||||||
|
|
||||||
import { dayjs } from '@/lib/dayjs';
|
import { dayjs } from '@/lib/dayjs';
|
||||||
|
|
||||||
|
const sampleBillingAddress = {
|
||||||
|
city: 'string',
|
||||||
|
country: 'string',
|
||||||
|
line1: 'string',
|
||||||
|
line2: 'string',
|
||||||
|
state: 'string',
|
||||||
|
zipCode: 'string',
|
||||||
|
//
|
||||||
|
id: 'string',
|
||||||
|
collectionId: 'string',
|
||||||
|
collectionName: 'string',
|
||||||
|
updated: 'string',
|
||||||
|
created: 'string',
|
||||||
|
};
|
||||||
|
|
||||||
export const SampleStudents = [
|
export const SampleStudents = [
|
||||||
{
|
{
|
||||||
id: 'STU-005',
|
id: 'STU-005',
|
||||||
@@ -13,6 +31,12 @@ export const SampleStudents = [
|
|||||||
quota: 50,
|
quota: 50,
|
||||||
status: 'active',
|
status: 'active',
|
||||||
createdAt: dayjs().subtract(1, 'hour').toDate(),
|
createdAt: dayjs().subtract(1, 'hour').toDate(),
|
||||||
|
billingAddress: sampleBillingAddress,
|
||||||
|
state: 'active',
|
||||||
|
timezone: '',
|
||||||
|
language: '',
|
||||||
|
currency: '',
|
||||||
|
collectionId: '',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: 'STU-004',
|
id: 'STU-004',
|
||||||
@@ -23,6 +47,12 @@ export const SampleStudents = [
|
|||||||
quota: 100,
|
quota: 100,
|
||||||
status: 'active',
|
status: 'active',
|
||||||
createdAt: dayjs().subtract(3, 'hour').toDate(),
|
createdAt: dayjs().subtract(3, 'hour').toDate(),
|
||||||
|
billingAddress: sampleBillingAddress,
|
||||||
|
state: 'active',
|
||||||
|
timezone: '',
|
||||||
|
language: '',
|
||||||
|
currency: '',
|
||||||
|
collectionId: '',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: 'STU-003',
|
id: 'STU-003',
|
||||||
@@ -33,6 +63,12 @@ export const SampleStudents = [
|
|||||||
quota: 10,
|
quota: 10,
|
||||||
status: 'blocked',
|
status: 'blocked',
|
||||||
createdAt: dayjs().subtract(1, 'hour').subtract(1, 'day').toDate(),
|
createdAt: dayjs().subtract(1, 'hour').subtract(1, 'day').toDate(),
|
||||||
|
billingAddress: sampleBillingAddress,
|
||||||
|
state: 'active',
|
||||||
|
timezone: '',
|
||||||
|
language: '',
|
||||||
|
currency: '',
|
||||||
|
collectionId: '',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: 'STU-002',
|
id: 'STU-002',
|
||||||
@@ -43,6 +79,12 @@ export const SampleStudents = [
|
|||||||
quota: 0,
|
quota: 0,
|
||||||
status: 'pending',
|
status: 'pending',
|
||||||
createdAt: dayjs().subtract(7, 'hour').subtract(1, 'day').toDate(),
|
createdAt: dayjs().subtract(7, 'hour').subtract(1, 'day').toDate(),
|
||||||
|
billingAddress: sampleBillingAddress,
|
||||||
|
state: 'active',
|
||||||
|
timezone: '',
|
||||||
|
language: '',
|
||||||
|
currency: '',
|
||||||
|
collectionId: '',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: 'STU-001',
|
id: 'STU-001',
|
||||||
@@ -53,5 +95,11 @@ export const SampleStudents = [
|
|||||||
quota: 50,
|
quota: 50,
|
||||||
status: 'active',
|
status: 'active',
|
||||||
createdAt: dayjs().subtract(2, 'hour').subtract(2, 'day').toDate(),
|
createdAt: dayjs().subtract(2, 'hour').subtract(2, 'day').toDate(),
|
||||||
|
billingAddress: sampleBillingAddress,
|
||||||
|
state: 'active',
|
||||||
|
timezone: '',
|
||||||
|
language: '',
|
||||||
|
currency: '',
|
||||||
|
collectionId: '',
|
||||||
},
|
},
|
||||||
] satisfies Student[];
|
] satisfies Student[];
|
||||||
|
@@ -120,12 +120,12 @@ export default function Page({ searchParams }: PageProps): React.JSX.Element {
|
|||||||
tempFilter.push(`phone ~ "%${phone}%"`);
|
tempFilter.push(`phone ~ "%${phone}%"`);
|
||||||
}
|
}
|
||||||
|
|
||||||
let preFinalListOption = { filter: '' };
|
let preFinalListOption = { filter: '', sort: '' };
|
||||||
if (tempFilter.length > 0) {
|
if (tempFilter.length > 0) {
|
||||||
preFinalListOption = { filter: tempFilter.join(' && ') };
|
preFinalListOption.filter = tempFilter.join(' && ');
|
||||||
}
|
}
|
||||||
if (tempSortDir.length > 0) {
|
if (tempSortDir.length > 0) {
|
||||||
preFinalListOption = { ...preFinalListOption, sort: tempSortDir };
|
preFinalListOption.sort = tempSortDir;
|
||||||
}
|
}
|
||||||
setListOption(preFinalListOption);
|
setListOption(preFinalListOption);
|
||||||
}, [sortDir, email, phone, state]);
|
}, [sortDir, email, phone, state]);
|
||||||
|
@@ -122,12 +122,12 @@ export default function Page({ searchParams }: PageProps): React.JSX.Element {
|
|||||||
tempFilter.push(`phone ~ "%${phone}%"`);
|
tempFilter.push(`phone ~ "%${phone}%"`);
|
||||||
}
|
}
|
||||||
|
|
||||||
let preFinalListOption = { filter: '' };
|
const preFinalListOption = { filter: '', sort: '' };
|
||||||
if (tempFilter.length > 0) {
|
if (tempFilter.length > 0) {
|
||||||
preFinalListOption = { filter: tempFilter.join(' && ') };
|
preFinalListOption.filter = tempFilter.join(' && ');
|
||||||
}
|
}
|
||||||
if (tempSortDir.length > 0) {
|
if (tempSortDir.length > 0) {
|
||||||
preFinalListOption = { ...preFinalListOption, sort: tempSortDir };
|
preFinalListOption.sort = tempSortDir;
|
||||||
}
|
}
|
||||||
setListOption(preFinalListOption);
|
setListOption(preFinalListOption);
|
||||||
}, [sortDir, email, phone, state]);
|
}, [sortDir, email, phone, state]);
|
||||||
|
@@ -22,7 +22,7 @@ import isDevelopment from '@/lib/check-is-development';
|
|||||||
import { logger } from '@/lib/default-logger';
|
import { logger } from '@/lib/default-logger';
|
||||||
import { pb } from '@/lib/pb';
|
import { pb } from '@/lib/pb';
|
||||||
import ErrorDisplay from '@/components/dashboard/error';
|
import ErrorDisplay from '@/components/dashboard/error';
|
||||||
import { defaultUserMeta } from '@/components/dashboard/user_meta/_constants';
|
import { defaultDBUserMeta, defaultUserMeta } from '@/components/dashboard/user_meta/_constants';
|
||||||
import type { UserMeta } from '@/components/dashboard/user_meta/type.d';
|
import type { UserMeta } from '@/components/dashboard/user_meta/type.d';
|
||||||
import { UserMetasFilters } from '@/components/dashboard/user_meta/user-metas-filters';
|
import { UserMetasFilters } from '@/components/dashboard/user_meta/user-metas-filters';
|
||||||
import { UserMetasPagination } from '@/components/dashboard/user_meta/user-metas-pagination';
|
import { UserMetasPagination } from '@/components/dashboard/user_meta/user-metas-pagination';
|
||||||
@@ -62,7 +62,7 @@ export default function Page({ searchParams }: PageProps): React.JSX.Element {
|
|||||||
.getList(currentPage + 1, rowsPerPage, listOption);
|
.getList(currentPage + 1, rowsPerPage, listOption);
|
||||||
const { items, totalItems } = models;
|
const { items, totalItems } = models;
|
||||||
const tempUserMeta: UserMeta[] = items.map((lt) => {
|
const tempUserMeta: UserMeta[] = items.map((lt) => {
|
||||||
return { ...defaultUserMeta, ...lt };
|
return lt as unknown as UserMeta;
|
||||||
});
|
});
|
||||||
|
|
||||||
setUserMetaData(tempUserMeta);
|
setUserMetaData(tempUserMeta);
|
||||||
@@ -115,12 +115,12 @@ export default function Page({ searchParams }: PageProps): React.JSX.Element {
|
|||||||
tempFilter.push(`phone ~ "%${phone}%"`);
|
tempFilter.push(`phone ~ "%${phone}%"`);
|
||||||
}
|
}
|
||||||
|
|
||||||
let preFinalListOption = { filter: '' };
|
const preFinalListOption = { filter: '', sort: '' };
|
||||||
if (tempFilter.length > 0) {
|
if (tempFilter.length > 0) {
|
||||||
preFinalListOption = { filter: tempFilter.join(' && ') };
|
preFinalListOption.filter = tempFilter.join(' && ');
|
||||||
}
|
}
|
||||||
if (tempSortDir.length > 0) {
|
if (tempSortDir.length > 0) {
|
||||||
preFinalListOption = { ...preFinalListOption, sort: tempSortDir };
|
preFinalListOption.sort = tempSortDir;
|
||||||
}
|
}
|
||||||
setListOption(preFinalListOption);
|
setListOption(preFinalListOption);
|
||||||
}, [sortDir, email, phone, state]);
|
}, [sortDir, email, phone, state]);
|
||||||
|
@@ -1,6 +1,7 @@
|
|||||||
'use client';
|
'use client';
|
||||||
|
|
||||||
import * as React from 'react';
|
import * as React from 'react';
|
||||||
|
import type { UserMeta } from '@/db/UserMetas/type';
|
||||||
import Avatar from '@mui/material/Avatar';
|
import Avatar from '@mui/material/Avatar';
|
||||||
import Card from '@mui/material/Card';
|
import Card from '@mui/material/Card';
|
||||||
import CardHeader from '@mui/material/CardHeader';
|
import CardHeader from '@mui/material/CardHeader';
|
||||||
@@ -13,8 +14,9 @@ import { useTranslation } from 'react-i18next';
|
|||||||
|
|
||||||
import { PropertyItem } from '@/components/core/property-item';
|
import { PropertyItem } from '@/components/core/property-item';
|
||||||
import { PropertyList } from '@/components/core/property-list';
|
import { PropertyList } from '@/components/core/property-list';
|
||||||
|
|
||||||
// import { CrCategory } from '@/components/dashboard/cr/categories/type';
|
// import { CrCategory } from '@/components/dashboard/cr/categories/type';
|
||||||
import type { UserMeta } from '@/components/dashboard/user_meta/type_move.d';
|
// import type { UserMeta } from '@/components/dashboard/user_meta/type_move.d';
|
||||||
|
|
||||||
export default function BasicDetailCard({
|
export default function BasicDetailCard({
|
||||||
userMeta,
|
userMeta,
|
||||||
|
@@ -12,6 +12,8 @@ import { SampleNotifications } from '@/app/dashboard/Sample/Notifications';
|
|||||||
import SamplePaymentCard from '@/app/dashboard/Sample/PaymentCard';
|
import SamplePaymentCard from '@/app/dashboard/Sample/PaymentCard';
|
||||||
import SampleSecurityCard from '@/app/dashboard/Sample/SecurityCard';
|
import SampleSecurityCard from '@/app/dashboard/Sample/SecurityCard';
|
||||||
import { COL_USER_METAS } from '@/constants';
|
import { COL_USER_METAS } from '@/constants';
|
||||||
|
import { defaultBillingAddress } from '@/db/billingAddress/constant';
|
||||||
|
import type { UserMeta } from '@/db/UserMetas/type';
|
||||||
import Box from '@mui/material/Box';
|
import Box from '@mui/material/Box';
|
||||||
import Link from '@mui/material/Link';
|
import Link from '@mui/material/Link';
|
||||||
import Stack from '@mui/material/Stack';
|
import Stack from '@mui/material/Stack';
|
||||||
@@ -20,7 +22,8 @@ import { ArrowLeft as ArrowLeftIcon } from '@phosphor-icons/react/dist/ssr/Arrow
|
|||||||
import type { RecordModel } from 'pocketbase';
|
import type { RecordModel } from 'pocketbase';
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
|
|
||||||
import { config } from '@/config';
|
// TODO: remove me
|
||||||
|
// import { config } from '@/config';
|
||||||
import { paths } from '@/paths';
|
import { paths } from '@/paths';
|
||||||
import { logger } from '@/lib/default-logger';
|
import { logger } from '@/lib/default-logger';
|
||||||
import { pb } from '@/lib/pb';
|
import { pb } from '@/lib/pb';
|
||||||
@@ -28,7 +31,7 @@ import { toast } from '@/components/core/toaster';
|
|||||||
import ErrorDisplay from '@/components/dashboard/error';
|
import ErrorDisplay from '@/components/dashboard/error';
|
||||||
import { defaultUserMeta } from '@/components/dashboard/user_meta/_constants';
|
import { defaultUserMeta } from '@/components/dashboard/user_meta/_constants';
|
||||||
import { Notifications } from '@/components/dashboard/user_meta/notifications';
|
import { Notifications } from '@/components/dashboard/user_meta/notifications';
|
||||||
import type { UserMeta } from '@/components/dashboard/user_meta/type_move.d';
|
// import type { UserMeta } from '@/components/dashboard/user_meta/type_move.d';
|
||||||
import FormLoading from '@/components/loading';
|
import FormLoading from '@/components/loading';
|
||||||
|
|
||||||
import BasicDetailCard from './BasicDetailCard';
|
import BasicDetailCard from './BasicDetailCard';
|
||||||
@@ -109,7 +112,7 @@ export default function Page(): React.JSX.Element {
|
|||||||
spacing={3}
|
spacing={3}
|
||||||
sx={{ alignItems: 'flex-start' }}
|
sx={{ alignItems: 'flex-start' }}
|
||||||
>
|
>
|
||||||
<TitleCard teacherRecord={userMeta} />
|
<TitleCard teacherRecord={{ ...userMeta, billingAddress: defaultBillingAddress }} />
|
||||||
</Stack>
|
</Stack>
|
||||||
</Stack>
|
</Stack>
|
||||||
<Grid
|
<Grid
|
||||||
|
@@ -145,7 +145,7 @@ export default function Page(): React.JSX.Element {
|
|||||||
spacing={2}
|
spacing={2}
|
||||||
sx={{ alignItems: 'center', flexWrap: 'wrap' }}
|
sx={{ alignItems: 'center', flexWrap: 'wrap' }}
|
||||||
>
|
>
|
||||||
<Typography variant="h4">{showLessonCategory.name}</Typography>
|
<Typography variant="h4">{showLessonCategory.word}</Typography>
|
||||||
<Chip
|
<Chip
|
||||||
icon={
|
icon={
|
||||||
<CheckCircleIcon
|
<CheckCircleIcon
|
||||||
@@ -221,7 +221,7 @@ export default function Page(): React.JSX.Element {
|
|||||||
),
|
),
|
||||||
},
|
},
|
||||||
{ key: 'word_c', value: showLessonCategory.word_c },
|
{ key: 'word_c', value: showLessonCategory.word_c },
|
||||||
{ key: 'Pos', value: showLessonCategory.pos },
|
{ key: 'Pos', value: showLessonCategory.id },
|
||||||
{
|
{
|
||||||
key: 'Visible',
|
key: 'Visible',
|
||||||
value: (
|
value: (
|
||||||
|
@@ -31,8 +31,8 @@ import { useUser } from '@/hooks/use-user';
|
|||||||
import { DynamicLogo } from '@/components/core/logo';
|
import { DynamicLogo } from '@/components/core/logo';
|
||||||
import { toast } from '@/components/core/toaster';
|
import { toast } from '@/components/core/toaster';
|
||||||
|
|
||||||
|
import { oAuthProviders } from '../o-auth-providers.tsx';
|
||||||
import type { OAuthProvider } from '../OAuthProvider';
|
import type { OAuthProvider } from '../OAuthProvider';
|
||||||
import { oAuthProviders } from '../oAuthProviders';
|
|
||||||
|
|
||||||
// interface OAuthProvider {
|
// interface OAuthProvider {
|
||||||
// id: 'google' | 'discord' | 'github';
|
// id: 'google' | 'discord' | 'github';
|
||||||
|
@@ -30,8 +30,8 @@ import { useUser } from '@/hooks/use-user';
|
|||||||
import { DynamicLogo } from '@/components/core/logo';
|
import { DynamicLogo } from '@/components/core/logo';
|
||||||
import { toast } from '@/components/core/toaster';
|
import { toast } from '@/components/core/toaster';
|
||||||
|
|
||||||
|
import { oAuthProviders } from '../o-auth-providers.tsx';
|
||||||
import type { OAuthProvider } from '../OAuthProvider';
|
import type { OAuthProvider } from '../OAuthProvider';
|
||||||
import { oAuthProviders } from '../oAuthProviders';
|
|
||||||
|
|
||||||
export function SignUpForm(): React.JSX.Element {
|
export function SignUpForm(): React.JSX.Element {
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
|
@@ -16,16 +16,36 @@ const user = {
|
|||||||
name: 'Sofia Rivers',
|
name: 'Sofia Rivers',
|
||||||
avatar: '/assets/avatar.png',
|
avatar: '/assets/avatar.png',
|
||||||
email: 'sofia@devias.io',
|
email: 'sofia@devias.io',
|
||||||
|
collectionId: '123321',
|
||||||
} satisfies User;
|
} satisfies User;
|
||||||
|
|
||||||
export function CommentAdd(): React.JSX.Element {
|
export function CommentAdd(): React.JSX.Element {
|
||||||
return (
|
return (
|
||||||
<Stack direction="row" spacing={2} sx={{ alignItems: 'flex-start' }}>
|
<Stack
|
||||||
|
direction="row"
|
||||||
|
spacing={2}
|
||||||
|
sx={{ alignItems: 'flex-start' }}
|
||||||
|
>
|
||||||
<Avatar src={user.avatar} />
|
<Avatar src={user.avatar} />
|
||||||
<Stack spacing={3} sx={{ flex: '1 1 auto' }}>
|
<Stack
|
||||||
<OutlinedInput multiline placeholder="Add a comment" rows={3} />
|
spacing={3}
|
||||||
<Stack direction="row" spacing={3} sx={{ alignItems: 'center', justifyContent: 'space-between' }}>
|
sx={{ flex: '1 1 auto' }}
|
||||||
<Stack direction="row" spacing={1} sx={{ alignItems: 'center' }}>
|
>
|
||||||
|
<OutlinedInput
|
||||||
|
multiline
|
||||||
|
placeholder="Add a comment"
|
||||||
|
rows={3}
|
||||||
|
/>
|
||||||
|
<Stack
|
||||||
|
direction="row"
|
||||||
|
spacing={3}
|
||||||
|
sx={{ alignItems: 'center', justifyContent: 'space-between' }}
|
||||||
|
>
|
||||||
|
<Stack
|
||||||
|
direction="row"
|
||||||
|
spacing={1}
|
||||||
|
sx={{ alignItems: 'center' }}
|
||||||
|
>
|
||||||
<IconButton sx={{ display: { sm: 'none' } }}>
|
<IconButton sx={{ display: { sm: 'none' } }}>
|
||||||
<PlusIcon />
|
<PlusIcon />
|
||||||
</IconButton>
|
</IconButton>
|
||||||
|
@@ -19,6 +19,7 @@ const user = {
|
|||||||
name: 'Sofia Rivers',
|
name: 'Sofia Rivers',
|
||||||
avatar: '/assets/avatar.png',
|
avatar: '/assets/avatar.png',
|
||||||
email: 'sofia@devias.io',
|
email: 'sofia@devias.io',
|
||||||
|
collectionId: '123321',
|
||||||
} satisfies User;
|
} satisfies User;
|
||||||
|
|
||||||
export interface MessageAddProps {
|
export interface MessageAddProps {
|
||||||
@@ -57,8 +58,15 @@ export function MessageAdd({ disabled = false, onSend }: MessageAddProps): React
|
|||||||
);
|
);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Stack direction="row" spacing={2} sx={{ alignItems: 'center', flex: '0 0 auto', px: 3, py: 1 }}>
|
<Stack
|
||||||
<Avatar src={user.avatar} sx={{ display: { xs: 'none', sm: 'inline' } }} />
|
direction="row"
|
||||||
|
spacing={2}
|
||||||
|
sx={{ alignItems: 'center', flex: '0 0 auto', px: 3, py: 1 }}
|
||||||
|
>
|
||||||
|
<Avatar
|
||||||
|
src={user.avatar}
|
||||||
|
sx={{ display: { xs: 'none', sm: 'inline' } }}
|
||||||
|
/>
|
||||||
<OutlinedInput
|
<OutlinedInput
|
||||||
disabled={disabled}
|
disabled={disabled}
|
||||||
onChange={handleChange}
|
onChange={handleChange}
|
||||||
@@ -67,7 +75,11 @@ export function MessageAdd({ disabled = false, onSend }: MessageAddProps): React
|
|||||||
sx={{ flex: '1 1 auto' }}
|
sx={{ flex: '1 1 auto' }}
|
||||||
value={content}
|
value={content}
|
||||||
/>
|
/>
|
||||||
<Stack direction="row" spacing={1} sx={{ alignItems: 'center' }}>
|
<Stack
|
||||||
|
direction="row"
|
||||||
|
spacing={1}
|
||||||
|
sx={{ alignItems: 'center' }}
|
||||||
|
>
|
||||||
<Tooltip title="Send">
|
<Tooltip title="Send">
|
||||||
<span>
|
<span>
|
||||||
<IconButton
|
<IconButton
|
||||||
@@ -84,24 +96,40 @@ export function MessageAdd({ disabled = false, onSend }: MessageAddProps): React
|
|||||||
</IconButton>
|
</IconButton>
|
||||||
</span>
|
</span>
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
<Stack direction="row" spacing={1} sx={{ display: { xs: 'none', sm: 'flex' } }}>
|
<Stack
|
||||||
|
direction="row"
|
||||||
|
spacing={1}
|
||||||
|
sx={{ display: { xs: 'none', sm: 'flex' } }}
|
||||||
|
>
|
||||||
<Tooltip title="Attach photo">
|
<Tooltip title="Attach photo">
|
||||||
<span>
|
<span>
|
||||||
<IconButton disabled={disabled} edge="end" onClick={handleAttach}>
|
<IconButton
|
||||||
|
disabled={disabled}
|
||||||
|
edge="end"
|
||||||
|
onClick={handleAttach}
|
||||||
|
>
|
||||||
<CameraIcon />
|
<CameraIcon />
|
||||||
</IconButton>
|
</IconButton>
|
||||||
</span>
|
</span>
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
<Tooltip title="Attach file">
|
<Tooltip title="Attach file">
|
||||||
<span>
|
<span>
|
||||||
<IconButton disabled={disabled} edge="end" onClick={handleAttach}>
|
<IconButton
|
||||||
|
disabled={disabled}
|
||||||
|
edge="end"
|
||||||
|
onClick={handleAttach}
|
||||||
|
>
|
||||||
<PaperclipIcon />
|
<PaperclipIcon />
|
||||||
</IconButton>
|
</IconButton>
|
||||||
</span>
|
</span>
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
</Stack>
|
</Stack>
|
||||||
</Stack>
|
</Stack>
|
||||||
<input hidden ref={fileInputRef} type="file" />
|
<input
|
||||||
|
hidden
|
||||||
|
ref={fileInputRef}
|
||||||
|
type="file"
|
||||||
|
/>
|
||||||
</Stack>
|
</Stack>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@@ -17,6 +17,7 @@ const user = {
|
|||||||
name: 'Sofia Rivers',
|
name: 'Sofia Rivers',
|
||||||
avatar: '/assets/avatar.png',
|
avatar: '/assets/avatar.png',
|
||||||
email: 'sofia@devias.io',
|
email: 'sofia@devias.io',
|
||||||
|
collectionId: '123321',
|
||||||
} satisfies User;
|
} satisfies User;
|
||||||
|
|
||||||
export interface MessageBoxProps {
|
export interface MessageBoxProps {
|
||||||
@@ -38,8 +39,14 @@ export function MessageBox({ message }: MessageBoxProps): React.JSX.Element {
|
|||||||
mr: position === 'left' ? 'auto' : 0,
|
mr: position === 'left' ? 'auto' : 0,
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<Avatar src={message.author.avatar} sx={{ '--Avatar-size': '32px' }} />
|
<Avatar
|
||||||
<Stack spacing={1} sx={{ flex: '1 1 auto' }}>
|
src={message.author.avatar}
|
||||||
|
sx={{ '--Avatar-size': '32px' }}
|
||||||
|
/>
|
||||||
|
<Stack
|
||||||
|
spacing={1}
|
||||||
|
sx={{ flex: '1 1 auto' }}
|
||||||
|
>
|
||||||
<Card
|
<Card
|
||||||
sx={{
|
sx={{
|
||||||
px: 2,
|
px: 2,
|
||||||
@@ -52,7 +59,11 @@ export function MessageBox({ message }: MessageBoxProps): React.JSX.Element {
|
|||||||
>
|
>
|
||||||
<Stack spacing={1}>
|
<Stack spacing={1}>
|
||||||
<div>
|
<div>
|
||||||
<Link color="inherit" sx={{ cursor: 'pointer' }} variant="subtitle2">
|
<Link
|
||||||
|
color="inherit"
|
||||||
|
sx={{ cursor: 'pointer' }}
|
||||||
|
variant="subtitle2"
|
||||||
|
>
|
||||||
{message.author.name}
|
{message.author.name}
|
||||||
</Link>
|
</Link>
|
||||||
</div>
|
</div>
|
||||||
@@ -66,14 +77,21 @@ export function MessageBox({ message }: MessageBoxProps): React.JSX.Element {
|
|||||||
/>
|
/>
|
||||||
) : null}
|
) : null}
|
||||||
{message.type === 'text' ? (
|
{message.type === 'text' ? (
|
||||||
<Typography color="inherit" variant="body1">
|
<Typography
|
||||||
|
color="inherit"
|
||||||
|
variant="body1"
|
||||||
|
>
|
||||||
{message.content}
|
{message.content}
|
||||||
</Typography>
|
</Typography>
|
||||||
) : null}
|
) : null}
|
||||||
</Stack>
|
</Stack>
|
||||||
</Card>
|
</Card>
|
||||||
<Box sx={{ display: 'flex', justifyContent: position === 'right' ? 'flex-end' : 'flex-start', px: 2 }}>
|
<Box sx={{ display: 'flex', justifyContent: position === 'right' ? 'flex-end' : 'flex-start', px: 2 }}>
|
||||||
<Typography color="text.secondary" noWrap variant="caption">
|
<Typography
|
||||||
|
color="text.secondary"
|
||||||
|
noWrap
|
||||||
|
variant="caption"
|
||||||
|
>
|
||||||
{dayjs(message.createdAt).fromNow()}
|
{dayjs(message.createdAt).fromNow()}
|
||||||
</Typography>
|
</Typography>
|
||||||
</Box>
|
</Box>
|
||||||
|
@@ -3,6 +3,7 @@
|
|||||||
import * as React from 'react';
|
import * as React from 'react';
|
||||||
import RouterLink from 'next/link';
|
import RouterLink from 'next/link';
|
||||||
import { usePathname } from 'next/navigation';
|
import { usePathname } from 'next/navigation';
|
||||||
|
import { MarkOneAsRead } from '@/db/Notifications/mark-one-as-read';
|
||||||
import Avatar from '@mui/material/Avatar';
|
import Avatar from '@mui/material/Avatar';
|
||||||
import Badge from '@mui/material/Badge';
|
import Badge from '@mui/material/Badge';
|
||||||
import Box from '@mui/material/Box';
|
import Box from '@mui/material/Box';
|
||||||
@@ -25,6 +26,7 @@ import type { NavItemConfig } from '@/types/nav';
|
|||||||
import type { NavColor } from '@/types/settings';
|
import type { NavColor } from '@/types/settings';
|
||||||
import type { User } from '@/types/user';
|
import type { User } from '@/types/user';
|
||||||
import { paths } from '@/paths';
|
import { paths } from '@/paths';
|
||||||
|
import { logger } from '@/lib/default-logger';
|
||||||
import { isNavItemActive } from '@/lib/is-nav-item-active';
|
import { isNavItemActive } from '@/lib/is-nav-item-active';
|
||||||
import { useDialog } from '@/hooks/use-dialog';
|
import { useDialog } from '@/hooks/use-dialog';
|
||||||
import { usePopover } from '@/hooks/use-popover';
|
import { usePopover } from '@/hooks/use-popover';
|
||||||
@@ -33,6 +35,7 @@ import { Dropdown } from '@/components/core/dropdown/dropdown';
|
|||||||
import { DropdownPopover } from '@/components/core/dropdown/dropdown-popover';
|
import { DropdownPopover } from '@/components/core/dropdown/dropdown-popover';
|
||||||
import { DropdownTrigger } from '@/components/core/dropdown/dropdown-trigger';
|
import { DropdownTrigger } from '@/components/core/dropdown/dropdown-trigger';
|
||||||
import { Logo } from '@/components/core/logo';
|
import { Logo } from '@/components/core/logo';
|
||||||
|
import { toast } from '@/components/core/toaster';
|
||||||
import { SearchDialog } from '@/components/dashboard/layout/search-dialog';
|
import { SearchDialog } from '@/components/dashboard/layout/search-dialog';
|
||||||
import type { ColorScheme } from '@/styles/theme/types';
|
import type { ColorScheme } from '@/styles/theme/types';
|
||||||
|
|
||||||
@@ -183,6 +186,29 @@ function SearchButton(): React.JSX.Element {
|
|||||||
|
|
||||||
function NotificationsButton(): React.JSX.Element {
|
function NotificationsButton(): React.JSX.Element {
|
||||||
const popover = usePopover<HTMLButtonElement>();
|
const popover = usePopover<HTMLButtonElement>();
|
||||||
|
const [listLength, setListLength] = React.useState<number>(0);
|
||||||
|
|
||||||
|
function handleMarkAllAsRead(): void {
|
||||||
|
// try {
|
||||||
|
// await MarkOneAsRead(id);
|
||||||
|
// toast.success('Notification marked as read');
|
||||||
|
// } catch (error) {
|
||||||
|
// logger.debug(error);
|
||||||
|
// toast.error('Something went wrong');
|
||||||
|
// }
|
||||||
|
}
|
||||||
|
|
||||||
|
function handleRemoveOne(id: string, cb: () => void): void {
|
||||||
|
MarkOneAsRead(id)
|
||||||
|
.then(() => {
|
||||||
|
toast.success('Notification marked as read');
|
||||||
|
cb();
|
||||||
|
})
|
||||||
|
.catch((error) => {
|
||||||
|
logger.debug(error);
|
||||||
|
toast.error('Something went wrong');
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<React.Fragment>
|
<React.Fragment>
|
||||||
@@ -190,15 +216,14 @@ function NotificationsButton(): React.JSX.Element {
|
|||||||
<Badge
|
<Badge
|
||||||
color="error"
|
color="error"
|
||||||
sx={{
|
sx={{
|
||||||
'& .MuiBadge-dot': {
|
'& .MuiBadge-badge': {
|
||||||
borderRadius: '50%',
|
height: '20px',
|
||||||
|
width: '20px',
|
||||||
right: '6px',
|
right: '6px',
|
||||||
top: '6px',
|
top: '6px',
|
||||||
height: '10px',
|
|
||||||
width: '10px',
|
|
||||||
},
|
},
|
||||||
}}
|
}}
|
||||||
variant="dot"
|
badgeContent={listLength}
|
||||||
>
|
>
|
||||||
<IconButton
|
<IconButton
|
||||||
onClick={popover.handleOpen}
|
onClick={popover.handleOpen}
|
||||||
@@ -212,6 +237,9 @@ function NotificationsButton(): React.JSX.Element {
|
|||||||
anchorEl={popover.anchorRef.current}
|
anchorEl={popover.anchorRef.current}
|
||||||
onClose={popover.handleClose}
|
onClose={popover.handleClose}
|
||||||
open={popover.open}
|
open={popover.open}
|
||||||
|
onMarkAllAsRead={handleMarkAllAsRead}
|
||||||
|
onRemoveOne={handleRemoveOne}
|
||||||
|
setListLength={setListLength}
|
||||||
/>
|
/>
|
||||||
</React.Fragment>
|
</React.Fragment>
|
||||||
);
|
);
|
||||||
|
@@ -1,6 +1,9 @@
|
|||||||
'use client';
|
'use client';
|
||||||
|
|
||||||
import type { Notification } from '@/db/Notifications/type';
|
import type { Notification } from '@/db/Notifications/type';
|
||||||
|
|
||||||
import { dayjs } from '@/lib/dayjs';
|
import { dayjs } from '@/lib/dayjs';
|
||||||
|
|
||||||
// import type { Notification } from './type.d.tsx';
|
// import type { Notification } from './type.d.tsx';
|
||||||
|
|
||||||
export const SampleNotifications = [
|
export const SampleNotifications = [
|
||||||
@@ -11,6 +14,11 @@ export const SampleNotifications = [
|
|||||||
type: 'new_job',
|
type: 'new_job',
|
||||||
author: { id: '0001', collectionId: '0001', name: 'Jie Yan', avatar: '/assets/avatar-8.png' },
|
author: { id: '0001', collectionId: '0001', name: 'Jie Yan', avatar: '/assets/avatar-8.png' },
|
||||||
job: { title: 'Remote React / React Native Developer' },
|
job: { title: 'Remote React / React Native Developer' },
|
||||||
|
//
|
||||||
|
created: '',
|
||||||
|
link: '',
|
||||||
|
NOTI_ID: '',
|
||||||
|
updated: '',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: 'EV-003',
|
id: 'EV-003',
|
||||||
@@ -19,6 +27,11 @@ export const SampleNotifications = [
|
|||||||
type: 'new_job',
|
type: 'new_job',
|
||||||
author: { id: '0001', collectionId: '0001', name: 'Fran Perez', avatar: '/assets/avatar-5.png' },
|
author: { id: '0001', collectionId: '0001', name: 'Fran Perez', avatar: '/assets/avatar-5.png' },
|
||||||
job: { title: 'Senior Golang Backend Engineer' },
|
job: { title: 'Senior Golang Backend Engineer' },
|
||||||
|
//
|
||||||
|
created: '',
|
||||||
|
link: '',
|
||||||
|
NOTI_ID: '',
|
||||||
|
updated: '',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: 'EV-002',
|
id: 'EV-002',
|
||||||
@@ -27,6 +40,11 @@ export const SampleNotifications = [
|
|||||||
type: 'new_feature',
|
type: 'new_feature',
|
||||||
author: { id: '0001', collectionId: '0001', name: 'Fran Perez', avatar: '/assets/avatar-5.png' },
|
author: { id: '0001', collectionId: '0001', name: 'Fran Perez', avatar: '/assets/avatar-5.png' },
|
||||||
description: 'Logistics management is now available',
|
description: 'Logistics management is now available',
|
||||||
|
//
|
||||||
|
created: '',
|
||||||
|
link: '',
|
||||||
|
NOTI_ID: '',
|
||||||
|
updated: '',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: 'EV-001',
|
id: 'EV-001',
|
||||||
@@ -35,5 +53,10 @@ export const SampleNotifications = [
|
|||||||
type: 'new_company',
|
type: 'new_company',
|
||||||
author: { id: '0001', collectionId: '002', name: 'Jie Yan', avatar: '/assets/avatar-8.png' },
|
author: { id: '0001', collectionId: '002', name: 'Jie Yan', avatar: '/assets/avatar-8.png' },
|
||||||
company: { name: 'Stripe' },
|
company: { name: 'Stripe' },
|
||||||
|
//
|
||||||
|
created: '',
|
||||||
|
link: '',
|
||||||
|
NOTI_ID: '',
|
||||||
|
updated: '',
|
||||||
},
|
},
|
||||||
] satisfies Notification[];
|
] satisfies Notification[];
|
||||||
|
@@ -6,11 +6,12 @@ import Badge from '@mui/material/Badge';
|
|||||||
import Box from '@mui/material/Box';
|
import Box from '@mui/material/Box';
|
||||||
|
|
||||||
import type { User } from '@/types/user';
|
import type { User } from '@/types/user';
|
||||||
|
import getImageUrlFromFile from '@/lib/get-image-url-from-file.ts';
|
||||||
import { usePopover } from '@/hooks/use-popover';
|
import { usePopover } from '@/hooks/use-popover';
|
||||||
|
import { useUser } from '@/hooks/use-user';
|
||||||
|
|
||||||
import { UserPopover } from '../../user-popover/user-popover';
|
import { UserPopover } from '../../user-popover/user-popover';
|
||||||
import { useUser } from '@/hooks/use-user';
|
|
||||||
import getImageUrlFromFile from '@/lib/get-image-url-from-file.ts';
|
|
||||||
// import { NotificationsButton } from './notifications-button';
|
// import { NotificationsButton } from './notifications-button';
|
||||||
|
|
||||||
// TODO:remove me
|
// TODO:remove me
|
||||||
@@ -50,7 +51,7 @@ export function UserButton(): React.JSX.Element {
|
|||||||
}}
|
}}
|
||||||
variant="dot"
|
variant="dot"
|
||||||
>
|
>
|
||||||
<Avatar src={getImageUrlFromFile(user.collectionId, user.id, user.avatar)} />
|
<Avatar src={getImageUrlFromFile(user.collectionId || '', user.id, user.avatar)} />
|
||||||
</Badge>
|
</Badge>
|
||||||
</Box>
|
</Box>
|
||||||
<UserPopover
|
<UserPopover
|
||||||
|
@@ -240,7 +240,7 @@ export function AccountDetails(): React.JSX.Element {
|
|||||||
</Stack>
|
</Stack>
|
||||||
</Box>
|
</Box>
|
||||||
<Avatar
|
<Avatar
|
||||||
src={getImageUrlFromFile(user.collectionId, user.id, user.avatar)}
|
src={getImageUrlFromFile(user.collectionId || '', user.id, user.avatar)}
|
||||||
sx={{ '--Avatar-size': '100px' }}
|
sx={{ '--Avatar-size': '100px' }}
|
||||||
/>
|
/>
|
||||||
</Box>
|
</Box>
|
||||||
|
@@ -21,7 +21,7 @@ import FormLoading from '@/components/loading';
|
|||||||
|
|
||||||
import ErrorDisplay from '../../error';
|
import ErrorDisplay from '../../error';
|
||||||
import { NavItem } from './nav-item';
|
import { NavItem } from './nav-item';
|
||||||
import { navItems } from './navItems';
|
import { navItems } from './nav-items';
|
||||||
|
|
||||||
export function SideNav(): React.JSX.Element {
|
export function SideNav(): React.JSX.Element {
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
@@ -99,7 +99,7 @@ export function SideNav(): React.JSX.Element {
|
|||||||
spacing={2}
|
spacing={2}
|
||||||
sx={{ alignItems: 'center' }}
|
sx={{ alignItems: 'center' }}
|
||||||
>
|
>
|
||||||
<Avatar src={getImageUrlFromFile(user.collectionId, user.id, user.avatar)}>{user.name}</Avatar>
|
<Avatar src={getImageUrlFromFile(user.collectionId || '', user.id, user.avatar)}>{user.name}</Avatar>
|
||||||
<div>
|
<div>
|
||||||
<Typography variant="subtitle1">{user.name}</Typography>
|
<Typography variant="subtitle1">{user.name}</Typography>
|
||||||
<Typography
|
<Typography
|
||||||
|
@@ -18,7 +18,7 @@ import { UsersThree as UsersThreeIcon } from '@phosphor-icons/react/dist/ssr/Use
|
|||||||
import type { NavItemConfig } from '@/types/nav';
|
import type { NavItemConfig } from '@/types/nav';
|
||||||
import { isNavItemActive } from '@/lib/is-nav-item-active';
|
import { isNavItemActive } from '@/lib/is-nav-item-active';
|
||||||
|
|
||||||
import { navItems } from './navItems';
|
import { navItems } from './nav-items';
|
||||||
|
|
||||||
const icons = {
|
const icons = {
|
||||||
'credit-card': CreditCardIcon,
|
'credit-card': CreditCardIcon,
|
||||||
|
@@ -2,7 +2,10 @@
|
|||||||
// default variable value for customer
|
// default variable value for customer
|
||||||
// empty valur for customer
|
// empty valur for customer
|
||||||
|
|
||||||
|
import { defaultBillingAddress } from '@/db/billingAddress/constant';
|
||||||
|
|
||||||
import { dayjs } from '@/lib/dayjs';
|
import { dayjs } from '@/lib/dayjs';
|
||||||
|
|
||||||
import type { Student } from './type.d';
|
import type { Student } from './type.d';
|
||||||
|
|
||||||
export const defaultStudent: Student = {
|
export const defaultStudent: Student = {
|
||||||
@@ -14,6 +17,12 @@ export const defaultStudent: Student = {
|
|||||||
quota: 0,
|
quota: 0,
|
||||||
status: 'pending',
|
status: 'pending',
|
||||||
createdAt: dayjs().toDate(),
|
createdAt: dayjs().toDate(),
|
||||||
|
billingAddress: defaultBillingAddress,
|
||||||
|
state: 'pending',
|
||||||
|
timezone: '',
|
||||||
|
language: '',
|
||||||
|
currency: '',
|
||||||
|
collectionId: '',
|
||||||
};
|
};
|
||||||
|
|
||||||
export const emptyLpCategory: Student = {
|
export const emptyLpCategory: Student = {
|
||||||
|
@@ -119,6 +119,7 @@ export interface StudentFiltersProps {
|
|||||||
filters?: Filters;
|
filters?: Filters;
|
||||||
sortDir?: SortDir;
|
sortDir?: SortDir;
|
||||||
fullData: Student[];
|
fullData: Student[];
|
||||||
|
//
|
||||||
}
|
}
|
||||||
|
|
||||||
// RULES: available filter options for student data
|
// RULES: available filter options for student data
|
||||||
@@ -126,4 +127,5 @@ export interface Filters {
|
|||||||
email?: string;
|
email?: string;
|
||||||
phone?: string;
|
phone?: string;
|
||||||
state?: string;
|
state?: string;
|
||||||
|
status?: string;
|
||||||
}
|
}
|
||||||
|
@@ -2,7 +2,10 @@
|
|||||||
// default variable value for customer
|
// default variable value for customer
|
||||||
// empty valur for customer
|
// empty valur for customer
|
||||||
|
|
||||||
|
import { defaultBillingAddress } from '@/db/billingAddress/constant';
|
||||||
|
|
||||||
import { dayjs } from '@/lib/dayjs';
|
import { dayjs } from '@/lib/dayjs';
|
||||||
|
|
||||||
import type { Teacher } from './type.d';
|
import type { Teacher } from './type.d';
|
||||||
|
|
||||||
export const defaultTeacher: Teacher = {
|
export const defaultTeacher: Teacher = {
|
||||||
@@ -14,6 +17,12 @@ export const defaultTeacher: Teacher = {
|
|||||||
quota: 0,
|
quota: 0,
|
||||||
status: 'pending',
|
status: 'pending',
|
||||||
createdAt: dayjs().toDate(),
|
createdAt: dayjs().toDate(),
|
||||||
|
billingAddress: defaultBillingAddress,
|
||||||
|
state: 'pending',
|
||||||
|
// timezone: '',
|
||||||
|
// language: '',
|
||||||
|
// currency: '',
|
||||||
|
collectionId: '',
|
||||||
};
|
};
|
||||||
|
|
||||||
export const emptyLpCategory: Teacher = {
|
export const emptyLpCategory: Teacher = {
|
||||||
|
@@ -181,7 +181,9 @@ export function TeacherEditForm(): React.JSX.Element {
|
|||||||
//
|
//
|
||||||
reset({ ...defaultValues, ...result });
|
reset({ ...defaultValues, ...result });
|
||||||
|
|
||||||
|
if (result?.billingAddress !== undefined) {
|
||||||
setBillingAddressId(result.billingAddress.id);
|
setBillingAddressId(result.billingAddress.id);
|
||||||
|
}
|
||||||
|
|
||||||
if (result.avatar) {
|
if (result.avatar) {
|
||||||
const fetchResult = await fetch(getImageUrlFromFile(result.collectionId, result.id, result.avatar));
|
const fetchResult = await fetch(getImageUrlFromFile(result.collectionId, result.id, result.avatar));
|
||||||
|
@@ -1,5 +1,7 @@
|
|||||||
'use client';
|
'use client';
|
||||||
|
|
||||||
|
import { BillingAddress } from '@/db/billingAddress/type';
|
||||||
|
|
||||||
// RULES: sorting direction for teacher lists
|
// RULES: sorting direction for teacher lists
|
||||||
export type SortDir = 'asc' | 'desc';
|
export type SortDir = 'asc' | 'desc';
|
||||||
|
|
||||||
@@ -14,7 +16,10 @@ export interface Teacher {
|
|||||||
email: string;
|
email: string;
|
||||||
phone?: string;
|
phone?: string;
|
||||||
quota: number;
|
quota: number;
|
||||||
|
//
|
||||||
|
// billingAddress: BillingAddress[] | [];
|
||||||
|
billingAddress: BillingAddress | Record<string, never>;
|
||||||
|
//
|
||||||
// status is obsoleted, replace by state
|
// status is obsoleted, replace by state
|
||||||
status: 'pending' | 'active' | 'blocked';
|
status: 'pending' | 'active' | 'blocked';
|
||||||
state: 'pending' | 'active' | 'blocked';
|
state: 'pending' | 'active' | 'blocked';
|
||||||
@@ -44,10 +49,13 @@ export interface CreateFormProps {
|
|||||||
timezone: string;
|
timezone: string;
|
||||||
language: string;
|
language: string;
|
||||||
currency: string;
|
currency: string;
|
||||||
|
// NOTE: fix this to file, change outside world
|
||||||
avatar?: File | null;
|
avatar?: File | null;
|
||||||
// quota?: number;
|
// quota?: number;
|
||||||
state?: 'pending' | 'active' | 'blocked';
|
state?: 'pending' | 'active' | 'blocked';
|
||||||
meta: Record<string, null>;
|
|
||||||
|
// TODO: obsolete
|
||||||
|
meta?: Record<string, null>;
|
||||||
}
|
}
|
||||||
|
|
||||||
// RULES: form data structure for editing existing teacher
|
// RULES: form data structure for editing existing teacher
|
@@ -1,20 +1,50 @@
|
|||||||
// RULES:
|
// RULES:
|
||||||
// default variable value for customer
|
// default variable value for customer
|
||||||
// empty valur for customer
|
// empty value for customer
|
||||||
|
|
||||||
|
import { defaultBillingAddress } from '@/db/billingAddress/constant';
|
||||||
|
import type { DBUserMeta, UserMeta } from '@/db/UserMetas/type';
|
||||||
|
|
||||||
import { dayjs } from '@/lib/dayjs';
|
import { dayjs } from '@/lib/dayjs';
|
||||||
|
|
||||||
import type { UserMeta } from './type.d';
|
// import type { UserMeta } from './type.d';
|
||||||
|
|
||||||
export const defaultUserMeta: UserMeta = {
|
export const defaultUserMeta: UserMeta = {
|
||||||
id: '',
|
id: '',
|
||||||
name: '',
|
name: '',
|
||||||
avatar: undefined,
|
avatar: '',
|
||||||
email: '',
|
email: '',
|
||||||
phone: undefined,
|
phone: '',
|
||||||
quota: 0,
|
quota: 0,
|
||||||
status: 'pending',
|
status: 'pending',
|
||||||
|
state: 'pending',
|
||||||
|
collectionId: '',
|
||||||
createdAt: dayjs().toDate(),
|
createdAt: dayjs().toDate(),
|
||||||
|
//
|
||||||
|
// billingAddress: defaultBillingAddress,
|
||||||
|
// state: 'active',
|
||||||
|
// timezone: '',
|
||||||
|
// language: '',
|
||||||
|
// currency: '',
|
||||||
|
// collectionId: '',
|
||||||
|
};
|
||||||
|
|
||||||
|
export const defaultDBUserMeta: DBUserMeta = {
|
||||||
|
id: '',
|
||||||
|
name: '',
|
||||||
|
avatar: '',
|
||||||
|
email: '',
|
||||||
|
phone: '',
|
||||||
|
quota: 0,
|
||||||
|
status: 'pending',
|
||||||
|
created: '1900-01-01',
|
||||||
|
state: 'active',
|
||||||
|
timezone: '',
|
||||||
|
language: '',
|
||||||
|
currency: '',
|
||||||
|
collectionId: '',
|
||||||
|
company: '',
|
||||||
|
expand: {},
|
||||||
};
|
};
|
||||||
|
|
||||||
export const emptyLpCategory: UserMeta = {
|
export const emptyLpCategory: UserMeta = {
|
||||||
|
@@ -3,6 +3,7 @@
|
|||||||
import * as React from 'react';
|
import * as React from 'react';
|
||||||
import RouterLink from 'next/link';
|
import RouterLink from 'next/link';
|
||||||
import { useRouter } from 'next/navigation';
|
import { useRouter } from 'next/navigation';
|
||||||
|
import { createTeacher } from '@/db/Teachers/Create';
|
||||||
import { zodResolver } from '@hookform/resolvers/zod';
|
import { zodResolver } from '@hookform/resolvers/zod';
|
||||||
import Avatar from '@mui/material/Avatar';
|
import Avatar from '@mui/material/Avatar';
|
||||||
import Box from '@mui/material/Box';
|
import Box from '@mui/material/Box';
|
||||||
@@ -26,11 +27,12 @@ import { Controller, useForm } from 'react-hook-form';
|
|||||||
import { z as zod } from 'zod';
|
import { z as zod } from 'zod';
|
||||||
|
|
||||||
import { paths } from '@/paths';
|
import { paths } from '@/paths';
|
||||||
|
import isDevelopment from '@/lib/check-is-development';
|
||||||
import { logger } from '@/lib/default-logger';
|
import { logger } from '@/lib/default-logger';
|
||||||
|
import { base64ToFile } from '@/lib/file-to-base64';
|
||||||
import { Option } from '@/components/core/option';
|
import { Option } from '@/components/core/option';
|
||||||
import { toast } from '@/components/core/toaster';
|
import { toast } from '@/components/core/toaster';
|
||||||
import { createTeacher } from '@/db/Teachers/Create';
|
import type { CreateFormProps } from '@/components/dashboard/teacher/type.d';
|
||||||
import isDevelopment from '@/lib/check-is-development';
|
|
||||||
|
|
||||||
function fileToBase64(file: Blob): Promise<string> {
|
function fileToBase64(file: Blob): Promise<string> {
|
||||||
return new Promise((resolve, reject) => {
|
return new Promise((resolve, reject) => {
|
||||||
@@ -45,6 +47,9 @@ function fileToBase64(file: Blob): Promise<string> {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// NOTE: face user side
|
||||||
|
// avatar should be the string inside image box,
|
||||||
|
// so avatar is string here
|
||||||
const schema = zod.object({
|
const schema = zod.object({
|
||||||
avatar: zod.string().optional(),
|
avatar: zod.string().optional(),
|
||||||
name: zod.string().min(1, 'Name is required').max(255),
|
name: zod.string().min(1, 'Name is required').max(255),
|
||||||
@@ -101,13 +106,25 @@ export function TeacherCreateForm(): React.JSX.Element {
|
|||||||
const onSubmit = React.useCallback(
|
const onSubmit = React.useCallback(
|
||||||
async (values: Values): Promise<void> => {
|
async (values: Values): Promise<void> => {
|
||||||
try {
|
try {
|
||||||
// Use standard create method from db/Customers/Create
|
const temp: CreateFormProps = {
|
||||||
const record = await createTeacher(values);
|
name: values.name,
|
||||||
toast.success('Customer created');
|
email: values.email,
|
||||||
|
timezone: values.timezone,
|
||||||
|
language: values.language,
|
||||||
|
currency: values.currency,
|
||||||
|
taxId: values.taxId,
|
||||||
|
avatar: values.avatar ? await base64ToFile(values.avatar) : null,
|
||||||
|
state: 'pending',
|
||||||
|
meta: {},
|
||||||
|
};
|
||||||
|
|
||||||
|
// some convert process here...
|
||||||
|
const record = await createTeacher(temp);
|
||||||
|
toast.success('Teacher created');
|
||||||
router.push(paths.dashboard.teachers.details(record.id));
|
router.push(paths.dashboard.teachers.details(record.id));
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
logger.error(err);
|
logger.error(err);
|
||||||
toast.error('Failed to create customer');
|
toast.error('Failed to create teacher');
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
[router]
|
[router]
|
||||||
@@ -157,7 +174,7 @@ export function TeacherCreateForm(): React.JSX.Element {
|
|||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<Avatar
|
<Avatar
|
||||||
src={avatar}
|
src={''}
|
||||||
sx={{
|
sx={{
|
||||||
'--Avatar-size': '100px',
|
'--Avatar-size': '100px',
|
||||||
'--Icon-fontSize': 'var(--icon-fontSize-lg)',
|
'--Icon-fontSize': 'var(--icon-fontSize-lg)',
|
||||||
|
@@ -1,7 +1,6 @@
|
|||||||
import type { CreateForm, Vocabulary } from './type';
|
import type { CreateForm, Vocabulary } from './type';
|
||||||
|
|
||||||
export const defaultVocabulary: Vocabulary = {
|
export const defaultVocabulary: Vocabulary = {
|
||||||
id: 'default-vocabulary-id',
|
|
||||||
image: undefined,
|
image: undefined,
|
||||||
sound: undefined,
|
sound: undefined,
|
||||||
word: '',
|
word: '',
|
||||||
@@ -11,11 +10,15 @@ export const defaultVocabulary: Vocabulary = {
|
|||||||
cat_id: 'default-category-id',
|
cat_id: 'default-category-id',
|
||||||
category: 'default-category',
|
category: 'default-category',
|
||||||
lesson_type_id: 'default-lesson-type-id',
|
lesson_type_id: 'default-lesson-type-id',
|
||||||
|
visible: 'visible',
|
||||||
|
//
|
||||||
|
id: 'default-vocabulary-id',
|
||||||
|
collectionId: '',
|
||||||
};
|
};
|
||||||
|
|
||||||
export const VocabularyCreateFormDefault: CreateForm = {
|
export const VocabularyCreateFormDefault: CreateForm = {
|
||||||
image: undefined,
|
image: null,
|
||||||
sound: undefined,
|
sound: '',
|
||||||
word: '',
|
word: '',
|
||||||
word_c: '',
|
word_c: '',
|
||||||
sample_e: '',
|
sample_e: '',
|
||||||
@@ -23,6 +26,7 @@ export const VocabularyCreateFormDefault: CreateForm = {
|
|||||||
cat_id: '',
|
cat_id: '',
|
||||||
category: '',
|
category: '',
|
||||||
lesson_type_id: '',
|
lesson_type_id: '',
|
||||||
|
visible: 'visible',
|
||||||
};
|
};
|
||||||
|
|
||||||
export const emptyVocabulary: Vocabulary = {
|
export const emptyVocabulary: Vocabulary = {
|
||||||
|
@@ -3,7 +3,7 @@
|
|||||||
export interface Vocabulary {
|
export interface Vocabulary {
|
||||||
created?: string;
|
created?: string;
|
||||||
updated?: string;
|
updated?: string;
|
||||||
image: string;
|
image: string | undefined;
|
||||||
sound?: string;
|
sound?: string;
|
||||||
word?: string;
|
word?: string;
|
||||||
word_c?: string;
|
word_c?: string;
|
||||||
@@ -22,6 +22,7 @@ export interface Vocabulary {
|
|||||||
//
|
//
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
isEmpty?: boolean;
|
||||||
//
|
//
|
||||||
id: string;
|
id: string;
|
||||||
collectionId: string;
|
collectionId: string;
|
||||||
@@ -62,3 +63,18 @@ export interface EditFormProps {
|
|||||||
export interface Helloworld {
|
export interface Helloworld {
|
||||||
helloworld: string;
|
helloworld: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// RULES: for use with vocabulary-create-form.tsx
|
||||||
|
// when you update, please take a look into `vocabulary-create-form.tsx`
|
||||||
|
export interface CreateForm {
|
||||||
|
image: File[] | null;
|
||||||
|
sound: string;
|
||||||
|
word: string;
|
||||||
|
word_c: string;
|
||||||
|
sample_e: string;
|
||||||
|
sample_c: string;
|
||||||
|
cat_id: string;
|
||||||
|
category: string;
|
||||||
|
lesson_type_id: string;
|
||||||
|
visible: string;
|
||||||
|
}
|
||||||
|
@@ -6,12 +6,16 @@ export const defaultNotification: Notification = {
|
|||||||
id: '',
|
id: '',
|
||||||
read: false,
|
read: false,
|
||||||
type: '',
|
type: '',
|
||||||
author: {},
|
//
|
||||||
job: {},
|
// TODO: troubleshoot
|
||||||
|
// author: {},
|
||||||
|
job: { title: '' },
|
||||||
description: '',
|
description: '',
|
||||||
NOTI_ID: '',
|
NOTI_ID: '',
|
||||||
created: '',
|
created: '',
|
||||||
updated: '',
|
updated: '',
|
||||||
|
link: '',
|
||||||
|
createdAt: new Date(),
|
||||||
};
|
};
|
||||||
|
|
||||||
export const emptyNotification: Notification = {
|
export const emptyNotification: Notification = {
|
||||||
|
@@ -16,6 +16,11 @@ export interface Notification {
|
|||||||
company?: { name: string };
|
company?: { name: string };
|
||||||
to_user_id?: User;
|
to_user_id?: User;
|
||||||
link: string;
|
link: string;
|
||||||
|
//
|
||||||
|
NOTI_ID: string;
|
||||||
|
//
|
||||||
|
created: string;
|
||||||
|
updated: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface CreateFormProps {
|
export interface CreateFormProps {
|
||||||
|
12
002_source/cms/src/db/UserMetas/type.d.ts
vendored
12
002_source/cms/src/db/UserMetas/type.d.ts
vendored
@@ -7,7 +7,7 @@ import type { BillingAddress } from '@/components/dashboard/user_meta/type.d';
|
|||||||
export interface DBUserMeta {
|
export interface DBUserMeta {
|
||||||
name: string;
|
name: string;
|
||||||
//
|
//
|
||||||
// NOTE: obslete "avatar" and use "avatar_file"
|
// NOTE: obsolete "avatar" and use "avatar_file"
|
||||||
avatar?: string;
|
avatar?: string;
|
||||||
avatar_file?: string;
|
avatar_file?: string;
|
||||||
//
|
//
|
||||||
@@ -35,20 +35,24 @@ export interface DBUserMeta {
|
|||||||
|
|
||||||
// UserMeta type definitions
|
// UserMeta type definitions
|
||||||
export interface UserMeta {
|
export interface UserMeta {
|
||||||
id: string;
|
|
||||||
name: string;
|
name: string;
|
||||||
avatar: string;
|
avatar: string;
|
||||||
email: string;
|
email: string;
|
||||||
phone: string;
|
phone: string;
|
||||||
quota: number;
|
quota: number;
|
||||||
|
state: 'active' | 'blocked' | 'pending';
|
||||||
|
// TODO: obsolete "status" field
|
||||||
status: 'active' | 'blocked' | 'pending';
|
status: 'active' | 'blocked' | 'pending';
|
||||||
|
//
|
||||||
|
id: string;
|
||||||
|
collectionId: string;
|
||||||
createdAt: Date;
|
createdAt: Date;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface UpdateUserMeta {
|
export interface UpdateUserMeta {
|
||||||
name?: string;
|
name?: string;
|
||||||
//
|
//
|
||||||
// NOTE: obslete "avatar" and use "avatar_file"
|
// NOTE: obsolete "avatar" and use "avatar_file"
|
||||||
// avatar_file?: string;
|
// avatar_file?: string;
|
||||||
avatar: File | null;
|
avatar: File | null;
|
||||||
//
|
//
|
||||||
@@ -57,7 +61,7 @@ export interface UpdateUserMeta {
|
|||||||
quota?: number;
|
quota?: number;
|
||||||
company?: string;
|
company?: string;
|
||||||
//
|
//
|
||||||
// relation handle seperately
|
// relation handle separately
|
||||||
// billingAddress: BillingAddress | Record<string, never>;
|
// billingAddress: BillingAddress | Record<string, never>;
|
||||||
|
|
||||||
// status is obsoleted, replace by state
|
// status is obsoleted, replace by state
|
||||||
|
@@ -8,9 +8,10 @@
|
|||||||
// - Must handle errors gracefully and provide user feedback
|
// - Must handle errors gracefully and provide user feedback
|
||||||
// - Should integrate with the authentication system
|
// - Should integrate with the authentication system
|
||||||
//
|
//
|
||||||
import { pb } from '@/lib/pb';
|
|
||||||
import { COL_USERS } from '@/constants';
|
import { COL_USERS } from '@/constants';
|
||||||
|
|
||||||
import type { User } from '@/types/user';
|
import type { User } from '@/types/user';
|
||||||
|
import { pb } from '@/lib/pb';
|
||||||
|
|
||||||
export async function createUser(userData: Partial<User>): Promise<{
|
export async function createUser(userData: Partial<User>): Promise<{
|
||||||
data?: User;
|
data?: User;
|
||||||
@@ -18,7 +19,7 @@ export async function createUser(userData: Partial<User>): Promise<{
|
|||||||
}> {
|
}> {
|
||||||
try {
|
try {
|
||||||
const data = await pb.collection(COL_USERS).create(userData);
|
const data = await pb.collection(COL_USERS).create(userData);
|
||||||
return { data };
|
return { data: data as unknown as User };
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
return { error: error as Error };
|
return { error: error as Error };
|
||||||
}
|
}
|
||||||
|
@@ -11,8 +11,8 @@ import { COL_VOCABULARIES } from '@/constants';
|
|||||||
|
|
||||||
import { pb } from '@/lib/pb';
|
import { pb } from '@/lib/pb';
|
||||||
|
|
||||||
import type { Vocabulary, VocabularyCreate } from './type';
|
import type { CreateForm, Vocabulary } from './type';
|
||||||
|
|
||||||
export default function createVocabulary(data: VocabularyCreate): Promise<Vocabulary> {
|
export default function createVocabulary(data: CreateForm): Promise<Vocabulary> {
|
||||||
return pb.collection(COL_VOCABULARIES).create(data);
|
return pb.collection(COL_VOCABULARIES).create(data);
|
||||||
}
|
}
|
||||||
|
@@ -11,8 +11,8 @@ import { COL_VOCABULARIES } from '@/constants';
|
|||||||
|
|
||||||
import { pb } from '@/lib/pb';
|
import { pb } from '@/lib/pb';
|
||||||
|
|
||||||
import type { Vocabularies } from './type';
|
import type { Vocabulary } from './type';
|
||||||
|
|
||||||
export default function getAllVocabularies(): Promise<Vocabularies> {
|
export default function getAllVocabularies(): Promise<Vocabulary[]> {
|
||||||
return pb.collection(COL_VOCABULARIES).getFullList();
|
return pb.collection(COL_VOCABULARIES).getFullList();
|
||||||
}
|
}
|
||||||
|
14
002_source/cms/src/db/billingAddress/constant.ts
Normal file
14
002_source/cms/src/db/billingAddress/constant.ts
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
export const defaultBillingAddress = {
|
||||||
|
city: '',
|
||||||
|
country: '',
|
||||||
|
line1: '',
|
||||||
|
line2: '',
|
||||||
|
state: '',
|
||||||
|
zipCode: '',
|
||||||
|
//
|
||||||
|
id: '',
|
||||||
|
collectionId: '',
|
||||||
|
collectionName: '',
|
||||||
|
updated: '',
|
||||||
|
created: '',
|
||||||
|
};
|
@@ -1,10 +1,12 @@
|
|||||||
export interface User {
|
export interface User {
|
||||||
id: string;
|
id: string;
|
||||||
name?: string;
|
name?: string;
|
||||||
avatar: string;
|
|
||||||
|
// comply original superbase example
|
||||||
|
avatar?: string | undefined;
|
||||||
email?: string;
|
email?: string;
|
||||||
|
|
||||||
collectionId: string;
|
collectionId?: string;
|
||||||
|
|
||||||
[key: string]: unknown;
|
[key: string]: unknown;
|
||||||
}
|
}
|
||||||
|
Reference in New Issue
Block a user