Compare commits

...

38 Commits

Author SHA1 Message Date
louiscklaw
3f13cc41bf ```update Enh 2025-05-24 08:51:32 +08:00
cb8643b405 ``update Add Docker build and deployment workflow for CMS and Ionic Mobile, update docker-compose.yml with new service images, and enhance documentation with strategy diagram and endpoint table`` 2025-05-24 08:21:33 +08:00
1157bf0fda ``update Update service address table formatting and placeholders for REQ0021`` 2025-05-24 08:15:44 +08:00
louiscklaw
2375c144a8 ``update Add build status message and server restart log in CMS entrypoint script`` 2025-05-24 06:11:33 +08:00
louiscklaw
24c62d940c ``update Migrate from custom Node image to official Node 22 image, install pnpm, update Dockerfile and entrypoint script`` 2025-05-24 06:02:46 +08:00
louiscklaw
e38e775fee ``update Simplify ENTRYPOINT syntax in PocketBase Dockerfile`` 2025-05-24 06:02:35 +08:00
louiscklaw
db9d3fbffd ``update Update port mappings for PocketBase, CMS, and Ionic Mobile services as per REQ0021`` 2025-05-24 05:48:45 +08:00
louiscklaw
368af36acd ``update Fix CPU resource limits format in docker-compose.db.yml and clean up dc.sh`` 2025-05-24 05:48:45 +08:00
d283b8274f update ticket, 2025-05-24 05:43:53 +08:00
7a8bd60185 update docker config, 2025-05-24 05:41:35 +08:00
a94ead44ba build cms ok, 2025-05-23 17:16:47 +08:00
50fc385c2b ``update Add todo-tree filter exclusions for node_modules, bundles, Next.js build folders, and distribution artifacts`` 2025-05-17 18:12:32 +08:00
91b3f53abe ``add Add database guidelines document outlining db driver structure and collection mappings`` 2025-05-17 18:12:19 +08:00
cf0ec8bd64 ``update Change logout behavior back to route navigation after sign-out instead of full page reload `` 2025-05-17 10:02:35 +00:00
59cdf7257b ```
update Implement standardized documentation for all database modules, including purpose, rules, and requirements for each CRUD operation; refactor existing comments to follow new documentation standard
```
2025-05-17 10:02:05 +00:00
b8309596be update example for ai, 2025-05-17 11:20:33 +08:00
688e102f2c ``delete Remove database schema definitions and hidden field count retrieval functionality`` 2025-05-17 09:41:36 +08:00
4d57d0a5f3 ``update Change logout behavior from route navigation to full page reload after sign-out `` 2025-05-16 23:41:22 +08:00
720838f137 Merge branch 'develop/ionic_mobile/i18n/trunk' into develop/ionic_mobile/login-flow/trunk 2025-05-16 23:38:56 +08:00
c92ac33ade ```
replace setting tab button with conditional rendering based on user authentication status, showing avatar if available
```
2025-05-16 23:37:39 +08:00
72e478937d ``update Add QR code generation feature with dynamic sizing and styling, implement screen-width-based conditional rendering, update i18n translations, and adjust context providers structure`` 2025-05-16 22:51:33 +08:00
62d8519da5 ``delete Remove i18n configuration file and English translation resources; likely replaced by a JSON-based or externalized translation management approach`` 2025-05-16 22:50:24 +08:00
8d746f3aa9 ``update Refactor navigation URLs by replacing hardcoded LESSON_LINK with Paths.LESSON_LINK across App, RouteConfig, and multiple components; remove redundant LESSON_LINK from constants`` 2025-05-16 22:00:04 +08:00
69b2ef59e5 ``update Upgrade dependencies including i18next, react-i18next, eslint, and testing-library; adjust peer dependencies and add new ESLint plugins`` 2025-05-16 21:59:52 +08:00
f9c0deb2e9 ``add Add CMS project code-workspace file to define multi-root workspace including documentation and AI workspace directories`` 2025-05-16 21:57:52 +08:00
47760fa7b2 ``add Add code-workspace files for documentation, ionic_mobile, and test projects to define multi-root workspace configurations`` 2025-05-16 21:57:41 +08:00
83bd86cc9b ``update Add .env.example file with PocketBase URL configuration for development environment `` 2025-05-16 17:47:33 +08:00
49189a532e ```
update Add development environment configuration, I18n support, route adjustments, and various hooks refactoring
```
2025-05-16 17:47:05 +08:00
6b917c9fb9 ``update Add error constant for empty PocketBase URL and validate it during app initialization `` 2025-05-16 15:56:56 +08:00
aa834a43c9 ``update Add platform-specific back button for Android, replace header layout, use LoadingSpinner, and refactor imports in Lesson page component`` 2025-05-16 15:56:46 +08:00
1775d8c5a3 ``update Remove unused components, imports, and refactor file URL generation in WordPage component`` 2025-05-16 15:56:24 +08:00
779062e247 ``update Replace getStudentAvatar with getStudentAvatarUrl for avatar URL generation in student profile`` 2025-05-16 15:56:19 +08:00
60df47fb8d ``update Extract PocketBase URL to constant and refactor related functions to use it for dynamic file URL generation`` 2025-05-16 15:55:47 +08:00
c87357ff24 ``update Remove unused import path suffix and comment out unused useRequireAuth hook`` 2025-05-16 15:55:30 +08:00
34a7ff7ac9 ``refactor Rename LoadingScreen component to LoadingSpinner and refactor structure for better reusability`` 2025-05-16 15:55:07 +08:00
3556e77a7c ```
add Implement custom sign-out functionality with loading state and error handling
```
2025-05-16 13:26:17 +08:00
57e25ef65f update fix eslint, 2025-05-16 12:51:32 +08:00
af5040ac6c update to use, 2025-05-16 12:07:58 +08:00
365 changed files with 4564 additions and 7681 deletions

View File

@@ -0,0 +1,44 @@
---
tags: docker
---
# description
docker and public endpoint
## strategy
```mermaid
graph LR
A["next build"]
B["docker build"]
A --> B --> deploy --> run
```
1. build on develop machine
1. docker build on develop machine (include source code)
1. push image to production machine
1. run image to production machine
## endpoint table
| function | address | | status |
| -------- | ---------------------------------------------------------------------------- | ---- | ---------------------------------------------------- |
| DB | [https://demo_ls_db.louislabs.com](https://demo_ls_db.louislabs.com) | 3013 | ![](https://status.iamon99.com/api/badge/108/uptime) |
| cms | [https://demo_ls_cms.louislabs.com](https://demo_ls_cms.louislabs.com) | 3011 | ![](https://status.iamon99.com/api/badge/109/uptime) |
| mobile | [https://demo_ls_mobile.louislabs.com](https://demo_ls_mobile.louislabs.com) | 3012 | ![](https://status.iamon99.com/api/badge/110/uptime) |
| T.B.A. | T.B.A. | 3014 | |
| T.B.A. | T.B.A. | 3015 | |
| T.B.A. | T.B.A. | 3016 | |
| T.B.A. | T.B.A. | 3017 | |
| T.B.A. | T.B.A. | 3018 | |
| T.B.A. | T.B.A. | 3019 | |
| T.B.A. | T.B.A. | 3010 | |
## status panel
[https://status.iamon99.com/status/demo-ls](https://status.iamon99.com/status/demo-ls)
## TODO
pending separated ssl certificate, currently using shared affine.louislabs.com

View File

@@ -22,3 +22,4 @@
- [REQ0018: family photo of frameworks](./REQ0018/index.md) - [REQ0018: family photo of frameworks](./REQ0018/index.md)
- [REQ0019: System architecture](./REQ0019/index.md) - [REQ0019: System architecture](./REQ0019/index.md)
- [REQ0020: Mobile login flow](./REQ0020/index.md) - [REQ0020: Mobile login flow](./REQ0020/index.md)
- [REQ0021: description](./REQ0021/index.md)

View File

@@ -2,3 +2,5 @@
**/*.log **/*.log
**/_archive **/_archive
**/_del **/_del
**/*copy*

View File

@@ -0,0 +1,40 @@
# Ignore all files and directories by default
-
# Except for the following:
!.dockerignore
!.gitignore
!.env.example
!.git/
!.env
!.vscode/
!.idea/
# Node.js specific
node_modules/
# Build artifacts
dist/
build/
# Logs
logs/
\*.log
# Environment files
.env\*
# Cache files
\*.cache
# OS generated files
.DS_Store
Thumbs.db

View File

@@ -82,7 +82,14 @@ module.exports = {
'eslintreact/jsx-sort-props': 'off', 'eslintreact/jsx-sort-props': 'off',
'react/jsx-sort-props': 'off', 'react/jsx-sort-props': 'off',
}, },
ignorePatterns: ['**/*del', '**/*bak', '**/*copy.*', '**/*copy*.*'], ignorePatterns: [
'**/*.del',
'**/*.bak',
'**/*copy.*',
'**/*copy*.*',
'**/*draft*/**',
//
],
overrides: [ overrides: [
{ {
// override to ignore no-def for `describe`, `it`, and `expect` // override to ignore no-def for `describe`, `it`, and `expect`

View File

@@ -3,6 +3,8 @@
**/*log **/*log
**/*tmp **/*tmp
.pnpm-store
.env .env
.env.production .env.production

17
002_source/cms/Dockerfile Normal file
View File

@@ -0,0 +1,17 @@
# Use official Node 18 base image
FROM node:22-slim
# Install pnpm globally
RUN npm install -g pnpm
# Copy your application code (optional, comment out if not needed)
COPY . /app
RUN cd /app && pnpm i
RUN cd /app && pnpm run build
# Set working directory
WORKDIR /app
# Default command (optional)
CMD ["./scripts/docker/entrypoint.sh"]

View File

@@ -10,7 +10,10 @@
"dev": "next dev -H 0.0.0.0", "dev": "next dev -H 0.0.0.0",
"build": "next build", "build": "next build",
"build:w": "pnpx nodemon --ext ts,tsx,json,mjs,js,jsx --delay 15 --exec \"pnpm run build\"", "build:w": "pnpx nodemon --ext ts,tsx,json,mjs,js,jsx --delay 15 --exec \"pnpm run build\"",
"start": "next start", "docker:push": "docker push 192.168.10.61:5000/demo_ls_cms ",
"docker:build": "docker build -t 192.168.10.61:5000/demo_ls_cms .",
"build:docker": "pnpm run build && pnpm run docker:build && pnpm run docker:push",
"start": "next start -H 0.0.0.0",
"lint": "next lint --quiet", "lint": "next lint --quiet",
"lint:fix": "next lint --fix", "lint:fix": "next lint --fix",
"lint:w": "pnpx nodemon --ext ts,tsx,json,mjs,js,jsx --delay 5 --exec \"pnpm run lint\"", "lint:w": "pnpx nodemon --ext ts,tsx,json,mjs,js,jsx --delay 5 --exec \"pnpm run lint\"",

View File

@@ -3,5 +3,11 @@
set -ex set -ex
reset reset
rm -rf .next
# rm -rf .next
# pnpm run typecheck
# pnpm run lint
# echo "check ok"
pnpm run build pnpm run build

18
002_source/cms/scripts/docker/entrypoint.sh Normal file → Executable file
View File

@@ -1,15 +1,19 @@
#!/usr/bin/env bash #!/usr/bin/env bash
# set -x set -x
# nvm use 20 while true; do
nvm alias default 18 pnpm run start
nvm use default
node -v
npm i -D echo "command restarted"
npm run dev sleep 5
done
# pnpm i -D
# pnpm run start
# while true; do # while true; do
# if [ "$NODE_ENV" = "development" ]; then # if [ "$NODE_ENV" = "development" ]; then

View File

@@ -1,3 +1,10 @@
{ {
"git.ignoreLimitWarning": true "git.ignoreLimitWarning": true,
} "todo-tree.filtering.excludeGlobs": [
"**/node_modules/*/**",
"**/*.bundle.*",
"**/.next/*/**",
"**/dist/*/**",
"**/build/*/**",
],
}

View File

@@ -1,21 +0,0 @@
/// <reference types="jest" />
import '@testing-library/jest-dom';
import { render, screen } from '@testing-library/react';
import Page from '@/app/_helloworld/page';
// Mock the translation hook
jest.mock('react-i18next', () => ({
useTranslation: () => ({
t: (key: string) => key,
}),
}));
describe('Page', () => {
it('renders a heading', () => {
render(<Page hello={'world'} />);
const heading = screen.getByRole('heading', { level: 1 });
expect(heading).toBeInTheDocument();
});
});

View File

@@ -1,9 +0,0 @@
import { render } from '@testing-library/react';
// CUT = Component Under Test
import CUT from '../components/_helloworld';
it('renders homepage unchanged', () => {
const { container } = render(<CUT />);
expect(container).toMatchSnapshot();
});

View File

@@ -1,5 +1,5 @@
import { dayjs } from '@/lib/dayjs'; import { dayjs } from '@/lib/dayjs';
import { LessonCategory } from '@/components/dashboard/lesson_category/type'; import type { LessonCategory } from '@/components/dashboard/lesson_category/type';
// import type { LessonCategory } from '@/components/dashboard/lp_categories/type'; // import type { LessonCategory } from '@/components/dashboard/lp_categories/type';

View File

@@ -28,12 +28,13 @@ import { PencilSimple as PencilSimpleIcon } from '@phosphor-icons/react/dist/ssr
import { Plus as PlusIcon } from '@phosphor-icons/react/dist/ssr/Plus'; import { Plus as PlusIcon } from '@phosphor-icons/react/dist/ssr/Plus';
import { ShieldWarning as ShieldWarningIcon } from '@phosphor-icons/react/dist/ssr/ShieldWarning'; import { ShieldWarning as ShieldWarningIcon } from '@phosphor-icons/react/dist/ssr/ShieldWarning';
import { User as UserIcon } from '@phosphor-icons/react/dist/ssr/User'; import { User as UserIcon } from '@phosphor-icons/react/dist/ssr/User';
import { RecordModel } from 'pocketbase'; import type { RecordModel } from 'pocketbase';
import { useTranslation } from 'react-i18next'; import { useTranslation } from 'react-i18next';
import { paths } from '@/paths'; import { paths } from '@/paths';
import { dayjs } from '@/lib/dayjs'; import { dayjs } from '@/lib/dayjs';
import { logger } from '@/lib/default-logger'; import { logger } from '@/lib/default-logger';
import getImageUrlFromFile from '@/lib/get-image-url-from-file.ts';
import { pb } from '@/lib/pb'; import { pb } from '@/lib/pb';
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';
@@ -45,7 +46,7 @@ import { Notifications } from '@/components/dashboard/lesson_category/notificati
import { Payments } from '@/components/dashboard/lesson_category/payments'; import { Payments } from '@/components/dashboard/lesson_category/payments';
import type { Address } from '@/components/dashboard/lesson_category/shipping-address'; import type { Address } from '@/components/dashboard/lesson_category/shipping-address';
import { ShippingAddress } from '@/components/dashboard/lesson_category/shipping-address'; import { ShippingAddress } from '@/components/dashboard/lesson_category/shipping-address';
import { LessonCategory } from '@/components/dashboard/lesson_category/type'; import type { LessonCategory } from '@/components/dashboard/lesson_category/type';
// import type { LessonCategory } from '@/components/dashboard/lp_categories/type'; // import type { LessonCategory } from '@/components/dashboard/lp_categories/type';
import FormLoading from '@/components/loading'; import FormLoading from '@/components/loading';
@@ -119,39 +120,72 @@ export default function Page(): React.JSX.Element {
{t('dashboard.lessonCategorys.list.title')} {t('dashboard.lessonCategorys.list.title')}
</Link> </Link>
</div> </div>
<Stack direction={{ xs: 'column', sm: 'row' }} spacing={3} sx={{ alignItems: 'flex-start' }}> <Stack
<Stack direction="row" spacing={2} sx={{ alignItems: 'center', flex: '1 1 auto' }}> direction={{ xs: 'column', sm: 'row' }}
spacing={3}
sx={{ alignItems: 'flex-start' }}
>
<Stack
direction="row"
spacing={2}
sx={{ alignItems: 'center', flex: '1 1 auto' }}
>
<Avatar <Avatar
src={`http://127.0.0.1:8090/api/files/${showLessonCategory.collectionId}/${showLessonCategory.id}/${showLessonCategory.cat_image}`} src={getImageUrlFromFile(
showLessonCategory.collectionId,
showLessonCategory.id,
showLessonCategory.cat_image
)}
sx={{ '--Avatar-size': '64px' }} sx={{ '--Avatar-size': '64px' }}
variant="rounded" variant="rounded"
> >
empty empty
</Avatar> </Avatar>
<div> <div>
<Stack direction="row" spacing={2} sx={{ alignItems: 'center', flexWrap: 'wrap' }}> <Stack
direction="row"
spacing={2}
sx={{ alignItems: 'center', flexWrap: 'wrap' }}
>
<Typography variant="h4">{showLessonCategory.name}</Typography> <Typography variant="h4">{showLessonCategory.name}</Typography>
<Chip <Chip
icon={<CheckCircleIcon color="var(--mui-palette-success-main)" weight="fill" />} icon={
<CheckCircleIcon
color="var(--mui-palette-success-main)"
weight="fill"
/>
}
label={showLessonCategory.visible} label={showLessonCategory.visible}
size="small" size="small"
variant="outlined" variant="outlined"
/> />
</Stack> </Stack>
<Typography color="text.secondary" variant="body1"> <Typography
color="text.secondary"
variant="body1"
>
{showLessonCategory.id} {showLessonCategory.id}
</Typography> </Typography>
</div> </div>
</Stack> </Stack>
<div> <div>
<Button endIcon={<CaretDownIcon />} variant="contained"> <Button
endIcon={<CaretDownIcon />}
variant="contained"
>
Action Action
</Button> </Button>
</div> </div>
</Stack> </Stack>
</Stack> </Stack>
<Grid container spacing={4}> <Grid
<Grid lg={4} xs={12}> container
spacing={4}
>
<Grid
lg={4}
xs={12}
>
<Stack spacing={4}> <Stack spacing={4}>
<Card> <Card>
<CardHeader <CardHeader
@@ -178,7 +212,16 @@ export default function Page(): React.JSX.Element {
> >
{( {(
[ [
{ key: 'Customer ID', value: <Chip label={showLessonCategory.id} size="small" variant="soft" /> }, {
key: 'Customer ID',
value: (
<Chip
label={showLessonCategory.id}
size="small"
variant="soft"
/>
),
},
{ key: 'Name', value: showLessonCategory.name }, { key: 'Name', value: showLessonCategory.name },
{ key: 'Pos', value: showLessonCategory.pos }, { key: 'Pos', value: showLessonCategory.pos },
{ {
@@ -195,9 +238,20 @@ export default function Page(): React.JSX.Element {
{ {
key: 'Quota', key: 'Quota',
value: ( value: (
<Stack direction="row" spacing={2} sx={{ alignItems: 'center' }}> <Stack
<LinearProgress sx={{ flex: '1 1 auto' }} value={50} variant="determinate" /> direction="row"
<Typography color="text.secondary" variant="body2"> spacing={2}
sx={{ alignItems: 'center' }}
>
<LinearProgress
sx={{ flex: '1 1 auto' }}
value={50}
variant="determinate"
/>
<Typography
color="text.secondary"
variant="body2"
>
50% 50%
</Typography> </Typography>
</Stack> </Stack>
@@ -206,7 +260,11 @@ export default function Page(): React.JSX.Element {
] satisfies { key: string; value: React.ReactNode }[] ] satisfies { key: string; value: React.ReactNode }[]
).map( ).map(
(item): React.JSX.Element => ( (item): React.JSX.Element => (
<PropertyItem key={item.key} name={item.key} value={item.value} /> <PropertyItem
key={item.key}
name={item.key}
value={item.value}
/>
) )
)} )}
</PropertyList> </PropertyList>
@@ -223,11 +281,17 @@ export default function Page(): React.JSX.Element {
<CardContent> <CardContent>
<Stack spacing={1}> <Stack spacing={1}>
<div> <div>
<Button color="error" variant="contained"> <Button
color="error"
variant="contained"
>
Delete account Delete account
</Button> </Button>
</div> </div>
<Typography color="text.secondary" variant="body2"> <Typography
color="text.secondary"
variant="body2"
>
A deleted lesson category cannot be restored. All data will be permanently removed. A deleted lesson category cannot be restored. All data will be permanently removed.
</Typography> </Typography>
</Stack> </Stack>
@@ -235,7 +299,10 @@ export default function Page(): React.JSX.Element {
</Card> </Card>
</Stack> </Stack>
</Grid> </Grid>
<Grid lg={8} xs={12}> <Grid
lg={8}
xs={12}
>
<Stack spacing={4}> <Stack spacing={4}>
<Payments <Payments
ordersValue={2069.48} ordersValue={2069.48}
@@ -282,7 +349,10 @@ export default function Page(): React.JSX.Element {
<Card> <Card>
<CardHeader <CardHeader
action={ action={
<Button color="secondary" startIcon={<PencilSimpleIcon />}> <Button
color="secondary"
startIcon={<PencilSimpleIcon />}
>
Edit Edit
</Button> </Button>
} }
@@ -294,8 +364,14 @@ export default function Page(): React.JSX.Element {
title={t('billing-details', { ns: 'lesson_category' })} title={t('billing-details', { ns: 'lesson_category' })}
/> />
<CardContent> <CardContent>
<Card sx={{ borderRadius: 1 }} variant="outlined"> <Card
<PropertyList divider={<Divider />} sx={{ '--PropertyItem-padding': '16px' }}> sx={{ borderRadius: 1 }}
variant="outlined"
>
<PropertyList
divider={<Divider />}
sx={{ '--PropertyItem-padding': '16px' }}
>
{( {(
[ [
{ key: 'Credit card', value: '**** 4142' }, { key: 'Credit card', value: '**** 4142' },
@@ -307,7 +383,11 @@ export default function Page(): React.JSX.Element {
] satisfies { key: string; value: React.ReactNode }[] ] satisfies { key: string; value: React.ReactNode }[]
).map( ).map(
(item): React.JSX.Element => ( (item): React.JSX.Element => (
<PropertyItem key={item.key} name={item.key} value={item.value} /> <PropertyItem
key={item.key}
name={item.key}
value={item.value}
/>
) )
)} )}
</PropertyList> </PropertyList>
@@ -317,7 +397,10 @@ export default function Page(): React.JSX.Element {
<Card> <Card>
<CardHeader <CardHeader
action={ action={
<Button color="secondary" startIcon={<PlusIcon />}> <Button
color="secondary"
startIcon={<PlusIcon />}
>
Add Add
</Button> </Button>
} }
@@ -329,7 +412,10 @@ export default function Page(): React.JSX.Element {
title={t('shipping-addresses', { ns: 'lesson_category' })} title={t('shipping-addresses', { ns: 'lesson_category' })}
/> />
<CardContent> <CardContent>
<Grid container spacing={3}> <Grid
container
spacing={3}
>
{( {(
[ [
{ {
@@ -351,7 +437,11 @@ export default function Page(): React.JSX.Element {
}, },
] satisfies Address[] ] satisfies Address[]
).map((address) => ( ).map((address) => (
<Grid key={address.id} md={6} xs={12}> <Grid
key={address.id}
md={6}
xs={12}
>
<ShippingAddress address={address} /> <ShippingAddress address={address} />
</Grid> </Grid>
))} ))}

View File

@@ -90,8 +90,7 @@ export default function Page({ searchParams }: PageProps): React.JSX.Element {
React.useEffect(() => { React.useEffect(() => {
if (!isFirstRun.current) { if (!isFirstRun.current) {
isFirstRun.current = true; isFirstRun.current = true;
} else { } else if (JSON.stringify(listOption) !== JSON.stringify(lastListOption)) {
if (JSON.stringify(listOption) !== JSON.stringify(lastListOption)) {
// reset page number as tab changes // reset page number as tab changes
setLastListOption(listOption); setLastListOption(listOption);
setCurrentPage(0); setCurrentPage(0);
@@ -99,7 +98,6 @@ export default function Page({ searchParams }: PageProps): React.JSX.Element {
} else { } else {
void reloadRows(); void reloadRows();
} }
}
}, [currentPage, rowsPerPage, listOption]); }, [currentPage, rowsPerPage, listOption]);
React.useEffect(() => { React.useEffect(() => {

View File

@@ -1,5 +1,5 @@
import { dayjs } from '@/lib/dayjs'; import { dayjs } from '@/lib/dayjs';
import { LessonCategory } from '@/components/dashboard/lesson_category/type'; import type { LessonCategory } from '@/components/dashboard/lesson_category/type';
export const LpCategoriesSampleData = [ export const LpCategoriesSampleData = [
{ {

View File

@@ -13,7 +13,7 @@ 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 type { CrCategory } from '@/components/dashboard/cr/categories/type';
export default function BasicDetailCard({ export default function BasicDetailCard({
lpModel: model, lpModel: model,

View File

@@ -10,12 +10,9 @@ import { CaretDown as CaretDownIcon } from '@phosphor-icons/react/dist/ssr/Caret
import { CheckCircle as CheckCircleIcon } from '@phosphor-icons/react/dist/ssr/CheckCircle'; import { CheckCircle as CheckCircleIcon } from '@phosphor-icons/react/dist/ssr/CheckCircle';
import { useTranslation } from 'react-i18next'; import { useTranslation } from 'react-i18next';
import getImageUrlFromFile from '@/lib/get-image-url-from-file.ts';
import type { CrCategory } from '@/components/dashboard/cr/categories/type'; import type { CrCategory } from '@/components/dashboard/cr/categories/type';
function getImageUrlFrRecord(record: CrCategory): string {
return `http://127.0.0.1:8090/api/files/${record.collectionId}/${record.id}/${record.cat_image}`;
}
export default function SampleTitleCard({ lpModel }: { lpModel: CrCategory }): React.JSX.Element { export default function SampleTitleCard({ lpModel }: { lpModel: CrCategory }): React.JSX.Element {
const { t } = useTranslation(); const { t } = useTranslation();
@@ -28,7 +25,7 @@ export default function SampleTitleCard({ lpModel }: { lpModel: CrCategory }): R
> >
<Avatar <Avatar
variant="rounded" variant="rounded"
src={getImageUrlFrRecord(lpModel)} src={getImageUrlFromFile(lpModel.collectionId, lpModel.id, lpModel.cat_image)}
sx={{ '--Avatar-size': '64px' }} sx={{ '--Avatar-size': '64px' }}
> >
{t('empty')} {t('empty')}

View File

@@ -13,7 +13,7 @@ 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 { LpCategory } from '@/components/dashboard/lp/categories/type'; import type { LpCategory } from '@/components/dashboard/lp/categories/type';
export default function BasicDetailCard({ export default function BasicDetailCard({
lpModel: model, lpModel: model,

View File

@@ -10,11 +10,8 @@ import { CaretDown as CaretDownIcon } from '@phosphor-icons/react/dist/ssr/Caret
import { CheckCircle as CheckCircleIcon } from '@phosphor-icons/react/dist/ssr/CheckCircle'; import { CheckCircle as CheckCircleIcon } from '@phosphor-icons/react/dist/ssr/CheckCircle';
import { useTranslation } from 'react-i18next'; import { useTranslation } from 'react-i18next';
import { LpCategory } from '@/components/dashboard/lp/categories/type'; import getImageUrlFromFile from '@/lib/get-image-url-from-file.ts';
import type { LpCategory } from '@/components/dashboard/lp/categories/type';
function getImageUrlFrRecord(record: LpCategory): string {
return `http://127.0.0.1:8090/api/files/${record.collectionId}/${record.id}/${record.cat_image}`;
}
export default function SampleTitleCard({ lpModel }: { lpModel: LpCategory }): React.JSX.Element { export default function SampleTitleCard({ lpModel }: { lpModel: LpCategory }): React.JSX.Element {
const { t } = useTranslation(); const { t } = useTranslation();
@@ -28,7 +25,7 @@ export default function SampleTitleCard({ lpModel }: { lpModel: LpCategory }): R
> >
<Avatar <Avatar
variant="rounded" variant="rounded"
src={getImageUrlFrRecord(lpModel)} src={getImageUrlFromFile(lpModel.collectionId, lpModel.id, lpModel.cat_image)}
sx={{ '--Avatar-size': '64px' }} sx={{ '--Avatar-size': '64px' }}
> >
{t('empty')} {t('empty')}

View File

@@ -1,5 +1,5 @@
import { dayjs } from '@/lib/dayjs'; import { dayjs } from '@/lib/dayjs';
import { LessonCategory } from '@/components/dashboard/lesson_category/type'; import type { LessonCategory } from '@/components/dashboard/lesson_category/type';
export const CrCategoriesSampleData = [ export const CrCategoriesSampleData = [
{ {

View File

@@ -9,14 +9,14 @@ import Typography from '@mui/material/Typography';
import { CaretDown as CaretDownIcon } from '@phosphor-icons/react/dist/ssr/CaretDown'; import { CaretDown as CaretDownIcon } from '@phosphor-icons/react/dist/ssr/CaretDown';
import { CheckCircle as CheckCircleIcon } from '@phosphor-icons/react/dist/ssr/CheckCircle'; import { CheckCircle as CheckCircleIcon } from '@phosphor-icons/react/dist/ssr/CheckCircle';
import { useTranslation } from 'react-i18next'; import { useTranslation } from 'react-i18next';
import type { Customer } from '@/components/dashboard/customer/type.d'; import type { Customer } from '@/components/dashboard/customer/type.d';
// import type { CrCategory } from '@/components/dashboard/cr/categories/type'; // import type { CrCategory } from '@/components/dashboard/cr/categories/type';
function getImageUrlFrRecord(record: Customer): string { function getImageUrlFrRecord(record: Customer): string {
// TODO: fix this // TODO: implement getImageUrlFrRecord
// `http://127.0.0.1:8090/api/files/${'record.collectionId'}/${'record.id'}/${'record.cat_image'}`; return 'not implemented';
return 'getImageUrlFrRecord(helloworld)';
} }
export default function SampleTitleCard({ lpModel }: { lpModel: Customer }): React.JSX.Element { export default function SampleTitleCard({ lpModel }: { lpModel: Customer }): React.JSX.Element {

View File

@@ -1,5 +1,5 @@
import { dayjs } from '@/lib/dayjs'; import { dayjs } from '@/lib/dayjs';
import { LessonCategory } from '@/components/dashboard/lesson_category/type'; import type { LessonCategory } from '@/components/dashboard/lesson_category/type';
// import type { LessonCategory } from '@/components/dashboard/lp_categories/type'; // import type { LessonCategory } from '@/components/dashboard/lp_categories/type';

View File

@@ -28,12 +28,13 @@ import { PencilSimple as PencilSimpleIcon } from '@phosphor-icons/react/dist/ssr
import { Plus as PlusIcon } from '@phosphor-icons/react/dist/ssr/Plus'; import { Plus as PlusIcon } from '@phosphor-icons/react/dist/ssr/Plus';
import { ShieldWarning as ShieldWarningIcon } from '@phosphor-icons/react/dist/ssr/ShieldWarning'; import { ShieldWarning as ShieldWarningIcon } from '@phosphor-icons/react/dist/ssr/ShieldWarning';
import { User as UserIcon } from '@phosphor-icons/react/dist/ssr/User'; import { User as UserIcon } from '@phosphor-icons/react/dist/ssr/User';
import { RecordModel } from 'pocketbase'; import type { RecordModel } from 'pocketbase';
import { useTranslation } from 'react-i18next'; import { useTranslation } from 'react-i18next';
import { paths } from '@/paths'; import { paths } from '@/paths';
import { dayjs } from '@/lib/dayjs'; import { dayjs } from '@/lib/dayjs';
import { logger } from '@/lib/default-logger'; import { logger } from '@/lib/default-logger';
import getImageUrlFromFile from '@/lib/get-image-url-from-file.ts';
import { pb } from '@/lib/pb'; import { pb } from '@/lib/pb';
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';
@@ -45,7 +46,7 @@ import { Notifications } from '@/components/dashboard/lesson_category/notificati
import { Payments } from '@/components/dashboard/lesson_category/payments'; import { Payments } from '@/components/dashboard/lesson_category/payments';
import type { Address } from '@/components/dashboard/lesson_category/shipping-address'; import type { Address } from '@/components/dashboard/lesson_category/shipping-address';
import { ShippingAddress } from '@/components/dashboard/lesson_category/shipping-address'; import { ShippingAddress } from '@/components/dashboard/lesson_category/shipping-address';
import { LessonCategory } from '@/components/dashboard/lesson_category/type'; import type { LessonCategory } from '@/components/dashboard/lesson_category/type';
// import type { LessonCategory } from '@/components/dashboard/lp_categories/type'; // import type { LessonCategory } from '@/components/dashboard/lp_categories/type';
import FormLoading from '@/components/loading'; import FormLoading from '@/components/loading';
@@ -119,39 +120,72 @@ export default function Page(): React.JSX.Element {
{t('dashboard.lessonCategorys.list.title')} {t('dashboard.lessonCategorys.list.title')}
</Link> </Link>
</div> </div>
<Stack direction={{ xs: 'column', sm: 'row' }} spacing={3} sx={{ alignItems: 'flex-start' }}> <Stack
<Stack direction="row" spacing={2} sx={{ alignItems: 'center', flex: '1 1 auto' }}> direction={{ xs: 'column', sm: 'row' }}
spacing={3}
sx={{ alignItems: 'flex-start' }}
>
<Stack
direction="row"
spacing={2}
sx={{ alignItems: 'center', flex: '1 1 auto' }}
>
<Avatar <Avatar
src={`http://127.0.0.1:8090/api/files/${showLessonCategory.collectionId}/${showLessonCategory.id}/${showLessonCategory.cat_image}`} src={getImageUrlFromFile(
showLessonCategory.collectionId,
showLessonCategory.id,
showLessonCategory.cat_image
)}
sx={{ '--Avatar-size': '64px' }} sx={{ '--Avatar-size': '64px' }}
variant="rounded" variant="rounded"
> >
empty empty
</Avatar> </Avatar>
<div> <div>
<Stack direction="row" spacing={2} sx={{ alignItems: 'center', flexWrap: 'wrap' }}> <Stack
direction="row"
spacing={2}
sx={{ alignItems: 'center', flexWrap: 'wrap' }}
>
<Typography variant="h4">{showLessonCategory.name}</Typography> <Typography variant="h4">{showLessonCategory.name}</Typography>
<Chip <Chip
icon={<CheckCircleIcon color="var(--mui-palette-success-main)" weight="fill" />} icon={
<CheckCircleIcon
color="var(--mui-palette-success-main)"
weight="fill"
/>
}
label={showLessonCategory.visible} label={showLessonCategory.visible}
size="small" size="small"
variant="outlined" variant="outlined"
/> />
</Stack> </Stack>
<Typography color="text.secondary" variant="body1"> <Typography
color="text.secondary"
variant="body1"
>
{showLessonCategory.id} {showLessonCategory.id}
</Typography> </Typography>
</div> </div>
</Stack> </Stack>
<div> <div>
<Button endIcon={<CaretDownIcon />} variant="contained"> <Button
endIcon={<CaretDownIcon />}
variant="contained"
>
Action Action
</Button> </Button>
</div> </div>
</Stack> </Stack>
</Stack> </Stack>
<Grid container spacing={4}> <Grid
<Grid lg={4} xs={12}> container
spacing={4}
>
<Grid
lg={4}
xs={12}
>
<Stack spacing={4}> <Stack spacing={4}>
<Card> <Card>
<CardHeader <CardHeader
@@ -178,7 +212,16 @@ export default function Page(): React.JSX.Element {
> >
{( {(
[ [
{ key: 'Customer ID', value: <Chip label={showLessonCategory.id} size="small" variant="soft" /> }, {
key: 'Customer ID',
value: (
<Chip
label={showLessonCategory.id}
size="small"
variant="soft"
/>
),
},
{ key: 'Name', value: showLessonCategory.name }, { key: 'Name', value: showLessonCategory.name },
{ key: 'Pos', value: showLessonCategory.pos }, { key: 'Pos', value: showLessonCategory.pos },
{ {
@@ -195,9 +238,20 @@ export default function Page(): React.JSX.Element {
{ {
key: 'Quota', key: 'Quota',
value: ( value: (
<Stack direction="row" spacing={2} sx={{ alignItems: 'center' }}> <Stack
<LinearProgress sx={{ flex: '1 1 auto' }} value={50} variant="determinate" /> direction="row"
<Typography color="text.secondary" variant="body2"> spacing={2}
sx={{ alignItems: 'center' }}
>
<LinearProgress
sx={{ flex: '1 1 auto' }}
value={50}
variant="determinate"
/>
<Typography
color="text.secondary"
variant="body2"
>
50% 50%
</Typography> </Typography>
</Stack> </Stack>
@@ -206,7 +260,11 @@ export default function Page(): React.JSX.Element {
] satisfies { key: string; value: React.ReactNode }[] ] satisfies { key: string; value: React.ReactNode }[]
).map( ).map(
(item): React.JSX.Element => ( (item): React.JSX.Element => (
<PropertyItem key={item.key} name={item.key} value={item.value} /> <PropertyItem
key={item.key}
name={item.key}
value={item.value}
/>
) )
)} )}
</PropertyList> </PropertyList>
@@ -223,11 +281,17 @@ export default function Page(): React.JSX.Element {
<CardContent> <CardContent>
<Stack spacing={1}> <Stack spacing={1}>
<div> <div>
<Button color="error" variant="contained"> <Button
color="error"
variant="contained"
>
Delete account Delete account
</Button> </Button>
</div> </div>
<Typography color="text.secondary" variant="body2"> <Typography
color="text.secondary"
variant="body2"
>
A deleted lesson category cannot be restored. All data will be permanently removed. A deleted lesson category cannot be restored. All data will be permanently removed.
</Typography> </Typography>
</Stack> </Stack>
@@ -235,7 +299,10 @@ export default function Page(): React.JSX.Element {
</Card> </Card>
</Stack> </Stack>
</Grid> </Grid>
<Grid lg={8} xs={12}> <Grid
lg={8}
xs={12}
>
<Stack spacing={4}> <Stack spacing={4}>
<Payments <Payments
ordersValue={2069.48} ordersValue={2069.48}
@@ -282,7 +349,10 @@ export default function Page(): React.JSX.Element {
<Card> <Card>
<CardHeader <CardHeader
action={ action={
<Button color="secondary" startIcon={<PencilSimpleIcon />}> <Button
color="secondary"
startIcon={<PencilSimpleIcon />}
>
Edit Edit
</Button> </Button>
} }
@@ -294,8 +364,14 @@ export default function Page(): React.JSX.Element {
title={t('billing-details', { ns: 'lesson_category' })} title={t('billing-details', { ns: 'lesson_category' })}
/> />
<CardContent> <CardContent>
<Card sx={{ borderRadius: 1 }} variant="outlined"> <Card
<PropertyList divider={<Divider />} sx={{ '--PropertyItem-padding': '16px' }}> sx={{ borderRadius: 1 }}
variant="outlined"
>
<PropertyList
divider={<Divider />}
sx={{ '--PropertyItem-padding': '16px' }}
>
{( {(
[ [
{ key: 'Credit card', value: '**** 4142' }, { key: 'Credit card', value: '**** 4142' },
@@ -307,7 +383,11 @@ export default function Page(): React.JSX.Element {
] satisfies { key: string; value: React.ReactNode }[] ] satisfies { key: string; value: React.ReactNode }[]
).map( ).map(
(item): React.JSX.Element => ( (item): React.JSX.Element => (
<PropertyItem key={item.key} name={item.key} value={item.value} /> <PropertyItem
key={item.key}
name={item.key}
value={item.value}
/>
) )
)} )}
</PropertyList> </PropertyList>
@@ -317,7 +397,10 @@ export default function Page(): React.JSX.Element {
<Card> <Card>
<CardHeader <CardHeader
action={ action={
<Button color="secondary" startIcon={<PlusIcon />}> <Button
color="secondary"
startIcon={<PlusIcon />}
>
Add Add
</Button> </Button>
} }
@@ -329,7 +412,10 @@ export default function Page(): React.JSX.Element {
title={t('shipping-addresses', { ns: 'lesson_category' })} title={t('shipping-addresses', { ns: 'lesson_category' })}
/> />
<CardContent> <CardContent>
<Grid container spacing={3}> <Grid
container
spacing={3}
>
{( {(
[ [
{ {
@@ -351,7 +437,11 @@ export default function Page(): React.JSX.Element {
}, },
] satisfies Address[] ] satisfies Address[]
).map((address) => ( ).map((address) => (
<Grid key={address.id} md={6} xs={12}> <Grid
key={address.id}
md={6}
xs={12}
>
<ShippingAddress address={address} /> <ShippingAddress address={address} />
</Grid> </Grid>
))} ))}

View File

@@ -1,5 +1,5 @@
import { dayjs } from '@/lib/dayjs'; import { dayjs } from '@/lib/dayjs';
import { LessonType } from '@/components/dashboard/lesson_type/lesson-type'; import type { LessonType } from '@/components/dashboard/lesson_type/lesson-type';
// import type { LessonType } from '@/components/dashboard/lesson_type/ILessonType'; // import type { LessonType } from '@/components/dashboard/lesson_type/ILessonType';

View File

@@ -1,5 +1,5 @@
import { dayjs } from '@/lib/dayjs'; import { dayjs } from '@/lib/dayjs';
import { LessonType } from '@/components/dashboard/lesson_type/lesson-type'; import type { LessonType } from '@/components/dashboard/lesson_type/lesson-type';
// import type { LessonType } from '@/components/dashboard/lesson_type/ILessonType'; // import type { LessonType } from '@/components/dashboard/lesson_type/ILessonType';

View File

@@ -88,8 +88,7 @@ export default function Page({ searchParams }: PageProps): React.JSX.Element {
React.useEffect(() => { React.useEffect(() => {
if (!isFirstRun.current) { if (!isFirstRun.current) {
isFirstRun.current = true; isFirstRun.current = true;
} else { } else if (JSON.stringify(listOption) !== JSON.stringify(lastListOption)) {
if (JSON.stringify(listOption) !== JSON.stringify(lastListOption)) {
// reset page number as tab changes // reset page number as tab changes
setLastListOption(listOption); setLastListOption(listOption);
setCurrentPage(0); setCurrentPage(0);
@@ -97,7 +96,6 @@ export default function Page({ searchParams }: PageProps): React.JSX.Element {
} else { } else {
void reloadRows(); void reloadRows();
} }
}
}, [currentPage, rowsPerPage, listOption]); }, [currentPage, rowsPerPage, listOption]);
React.useEffect(() => { React.useEffect(() => {

View File

@@ -1,5 +1,5 @@
import { dayjs } from '@/lib/dayjs'; import { dayjs } from '@/lib/dayjs';
import { LessonCategory } from '@/components/dashboard/lesson_category/type'; import type { LessonCategory } from '@/components/dashboard/lesson_category/type';
export const LpCategoriesSampleData = [ export const LpCategoriesSampleData = [
{ {

View File

@@ -13,7 +13,7 @@ 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 { LpCategory } from '@/components/dashboard/lp/categories/type'; import type { LpCategory } from '@/components/dashboard/lp/categories/type';
export default function BasicDetailCard({ export default function BasicDetailCard({
lpModel: model, lpModel: model,

View File

@@ -10,11 +10,8 @@ import { CaretDown as CaretDownIcon } from '@phosphor-icons/react/dist/ssr/Caret
import { CheckCircle as CheckCircleIcon } from '@phosphor-icons/react/dist/ssr/CheckCircle'; import { CheckCircle as CheckCircleIcon } from '@phosphor-icons/react/dist/ssr/CheckCircle';
import { useTranslation } from 'react-i18next'; import { useTranslation } from 'react-i18next';
import { LpCategory } from '@/components/dashboard/lp/categories/type'; import getImageUrlFromFile from '@/lib/get-image-url-from-file.ts';
import type { LpCategory } from '@/components/dashboard/lp/categories/type';
function getImageUrlFrRecord(record: LpCategory): string {
return `http://127.0.0.1:8090/api/files/${record.collectionId}/${record.id}/${record.cat_image}`;
}
export default function SampleTitleCard({ lpModel }: { lpModel: LpCategory }): React.JSX.Element { export default function SampleTitleCard({ lpModel }: { lpModel: LpCategory }): React.JSX.Element {
const { t } = useTranslation(); const { t } = useTranslation();
@@ -28,7 +25,7 @@ export default function SampleTitleCard({ lpModel }: { lpModel: LpCategory }): R
> >
<Avatar <Avatar
variant="rounded" variant="rounded"
src={getImageUrlFrRecord(lpModel)} src={getImageUrlFromFile(lpModel.collectionId, lpModel.id, lpModel.cat_image)}
sx={{ '--Avatar-size': '64px' }} sx={{ '--Avatar-size': '64px' }}
> >
{t('empty')} {t('empty')}

View File

@@ -88,8 +88,7 @@ export default function Page({ searchParams }: PageProps): React.JSX.Element {
React.useEffect(() => { React.useEffect(() => {
if (!isFirstRun.current) { if (!isFirstRun.current) {
isFirstRun.current = true; isFirstRun.current = true;
} else { } else if (JSON.stringify(listOption) !== JSON.stringify(lastListOption)) {
if (JSON.stringify(listOption) !== JSON.stringify(lastListOption)) {
// reset page number as tab changes // reset page number as tab changes
setLastListOption(listOption); setLastListOption(listOption);
setCurrentPage(0); setCurrentPage(0);
@@ -97,7 +96,6 @@ export default function Page({ searchParams }: PageProps): React.JSX.Element {
} else { } else {
void reloadRows(); void reloadRows();
} }
}
}, [currentPage, rowsPerPage, listOption]); }, [currentPage, rowsPerPage, listOption]);
React.useEffect(() => { React.useEffect(() => {

View File

@@ -1,5 +1,5 @@
import { dayjs } from '@/lib/dayjs'; import { dayjs } from '@/lib/dayjs';
import { LessonCategory } from '@/components/dashboard/lesson_category/type'; import type { LessonCategory } from '@/components/dashboard/lesson_category/type';
export const LpCategoriesSampleData = [ export const LpCategoriesSampleData = [
{ {

View File

@@ -13,7 +13,7 @@ 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 { LpCategory } from '@/components/dashboard/lp/categories/type'; import type { LpCategory } from '@/components/dashboard/lp/categories/type';
export default function BasicDetailCard({ export default function BasicDetailCard({
lpModel: model, lpModel: model,

View File

@@ -10,11 +10,8 @@ import { CaretDown as CaretDownIcon } from '@phosphor-icons/react/dist/ssr/Caret
import { CheckCircle as CheckCircleIcon } from '@phosphor-icons/react/dist/ssr/CheckCircle'; import { CheckCircle as CheckCircleIcon } from '@phosphor-icons/react/dist/ssr/CheckCircle';
import { useTranslation } from 'react-i18next'; import { useTranslation } from 'react-i18next';
import { LpCategory } from '@/components/dashboard/lp/categories/type'; import getImageUrlFromFile from '@/lib/get-image-url-from-file.ts';
import type { LpCategory } from '@/components/dashboard/lp/categories/type';
function getImageUrlFrRecord(record: LpCategory): string {
return `http://127.0.0.1:8090/api/files/${record.collectionId}/${record.id}/${record.cat_image}`;
}
export default function SampleTitleCard({ lpModel }: { lpModel: LpCategory }): React.JSX.Element { export default function SampleTitleCard({ lpModel }: { lpModel: LpCategory }): React.JSX.Element {
const { t } = useTranslation(); const { t } = useTranslation();
@@ -28,7 +25,7 @@ export default function SampleTitleCard({ lpModel }: { lpModel: LpCategory }): R
> >
<Avatar <Avatar
variant="rounded" variant="rounded"
src={getImageUrlFrRecord(lpModel)} src={getImageUrlFromFile(lpModel.collectionId, lpModel.id, lpModel.cat_image)}
sx={{ '--Avatar-size': '64px' }} sx={{ '--Avatar-size': '64px' }}
> >
{t('empty')} {t('empty')}

View File

@@ -86,8 +86,7 @@ export default function Page({ searchParams }: PageProps): React.JSX.Element {
React.useEffect(() => { React.useEffect(() => {
if (!isFirstRun.current) { if (!isFirstRun.current) {
isFirstRun.current = true; isFirstRun.current = true;
} else { } else if (JSON.stringify(listOption) !== JSON.stringify(lastListOption)) {
if (JSON.stringify(listOption) !== JSON.stringify(lastListOption)) {
// reset page number as tab changes // reset page number as tab changes
setLastListOption(listOption); setLastListOption(listOption);
setCurrentPage(0); setCurrentPage(0);
@@ -95,7 +94,6 @@ export default function Page({ searchParams }: PageProps): React.JSX.Element {
} else { } else {
void reloadRows(); void reloadRows();
} }
}
}, [currentPage, rowsPerPage, listOption]); }, [currentPage, rowsPerPage, listOption]);
React.useEffect(() => { React.useEffect(() => {

View File

@@ -1,5 +1,5 @@
import { dayjs } from '@/lib/dayjs'; import { dayjs } from '@/lib/dayjs';
import { LessonCategory } from '@/components/dashboard/lesson_category/type'; import type { LessonCategory } from '@/components/dashboard/lesson_category/type';
export const LpCategoriesSampleData = [ export const LpCategoriesSampleData = [
{ {

View File

@@ -10,12 +10,9 @@ import { CaretDown as CaretDownIcon } from '@phosphor-icons/react/dist/ssr/Caret
import { CheckCircle as CheckCircleIcon } from '@phosphor-icons/react/dist/ssr/CheckCircle'; import { CheckCircle as CheckCircleIcon } from '@phosphor-icons/react/dist/ssr/CheckCircle';
import { useTranslation } from 'react-i18next'; import { useTranslation } from 'react-i18next';
import getImageUrlFromFile from '@/lib/get-image-url-from-file.ts';
import type { MfCategory } from '@/components/dashboard/mf/categories/type'; import type { MfCategory } from '@/components/dashboard/mf/categories/type';
function getImageUrlFrRecord(record: MfCategory): string {
return `http://127.0.0.1:8090/api/files/${record.collectionId}/${record.id}/${record.cat_image}`;
}
export default function SampleTitleCard({ lpModel }: { lpModel: MfCategory }): React.JSX.Element { export default function SampleTitleCard({ lpModel }: { lpModel: MfCategory }): React.JSX.Element {
const { t } = useTranslation(); const { t } = useTranslation();
@@ -28,7 +25,7 @@ export default function SampleTitleCard({ lpModel }: { lpModel: MfCategory }): R
> >
<Avatar <Avatar
variant="rounded" variant="rounded"
src={getImageUrlFrRecord(lpModel)} src={getImageUrlFromFile(lpModel.collectionId, lpModel.id, lpModel.cat_image)}
sx={{ '--Avatar-size': '64px' }} sx={{ '--Avatar-size': '64px' }}
> >
{t('empty')} {t('empty')}

View File

@@ -86,8 +86,7 @@ export default function Page({ searchParams }: PageProps): React.JSX.Element {
React.useEffect(() => { React.useEffect(() => {
if (!isFirstRun.current) { if (!isFirstRun.current) {
isFirstRun.current = true; isFirstRun.current = true;
} else { } else if (JSON.stringify(listOption) !== JSON.stringify(lastListOption)) {
if (JSON.stringify(listOption) !== JSON.stringify(lastListOption)) {
// reset page number as tab changes // reset page number as tab changes
setLastListOption(listOption); setLastListOption(listOption);
setCurrentPage(0); setCurrentPage(0);
@@ -95,7 +94,6 @@ export default function Page({ searchParams }: PageProps): React.JSX.Element {
} else { } else {
void reloadRows(); void reloadRows();
} }
}
}, [currentPage, rowsPerPage, listOption]); }, [currentPage, rowsPerPage, listOption]);
React.useEffect(() => { React.useEffect(() => {

View File

@@ -1,5 +1,5 @@
import { dayjs } from '@/lib/dayjs'; import { dayjs } from '@/lib/dayjs';
import { LessonCategory } from '@/components/dashboard/lesson_category/type'; import type { LessonCategory } from '@/components/dashboard/lesson_category/type';
export const LpCategoriesSampleData = [ export const LpCategoriesSampleData = [
{ {

View File

@@ -13,7 +13,7 @@ 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 { MfCategory } from '@/components/dashboard/mf/categories/type'; import type { MfCategory } from '@/components/dashboard/mf/categories/type';
export default function BasicDetailCard({ export default function BasicDetailCard({
lpModel: model, lpModel: model,

View File

@@ -10,12 +10,9 @@ import { CaretDown as CaretDownIcon } from '@phosphor-icons/react/dist/ssr/Caret
import { CheckCircle as CheckCircleIcon } from '@phosphor-icons/react/dist/ssr/CheckCircle'; import { CheckCircle as CheckCircleIcon } from '@phosphor-icons/react/dist/ssr/CheckCircle';
import { useTranslation } from 'react-i18next'; import { useTranslation } from 'react-i18next';
import getImageUrlFromFile from '@/lib/get-image-url-from-file.ts';
import type { MfCategory } from '@/components/dashboard/mf/categories/type'; import type { MfCategory } from '@/components/dashboard/mf/categories/type';
function getImageUrlFrRecord(record: MfCategory): string {
return `http://127.0.0.1:8090/api/files/${record.collectionId}/${record.id}/${record.cat_image}`;
}
export default function SampleTitleCard({ lpModel }: { lpModel: MfCategory }): React.JSX.Element { export default function SampleTitleCard({ lpModel }: { lpModel: MfCategory }): React.JSX.Element {
const { t } = useTranslation(); const { t } = useTranslation();
@@ -28,7 +25,7 @@ export default function SampleTitleCard({ lpModel }: { lpModel: MfCategory }): R
> >
<Avatar <Avatar
variant="rounded" variant="rounded"
src={getImageUrlFrRecord(lpModel)} src={getImageUrlFromFile(lpModel.collectionId, lpModel.id, lpModel.cat_image)}
sx={{ '--Avatar-size': '64px' }} sx={{ '--Avatar-size': '64px' }}
> >
{t('empty')} {t('empty')}

View File

@@ -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[];

View File

@@ -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]);

View File

@@ -9,14 +9,10 @@ import Typography from '@mui/material/Typography';
import { CaretDown as CaretDownIcon } from '@phosphor-icons/react/dist/ssr/CaretDown'; import { CaretDown as CaretDownIcon } from '@phosphor-icons/react/dist/ssr/CaretDown';
import { CheckCircle as CheckCircleIcon } from '@phosphor-icons/react/dist/ssr/CheckCircle'; import { CheckCircle as CheckCircleIcon } from '@phosphor-icons/react/dist/ssr/CheckCircle';
import { useTranslation } from 'react-i18next'; import { useTranslation } from 'react-i18next';
import getImageUrlFromFile from '@/lib/get-image-url-from-file.ts';
import type { Student } from '@/components/dashboard/student/type.d'; import type { Student } from '@/components/dashboard/student/type.d';
// import type { CrCategory } from '@/components/dashboard/cr/categories/type';
function getImageUrlForStudent(student: Student): string {
return `http://127.0.0.1:8090/api/files/${student.collectionId}/${student.id}/${student.avatar}`;
}
export default function SampleTitleCard({ studentRecord }: { studentRecord: Student }): React.JSX.Element { export default function SampleTitleCard({ studentRecord }: { studentRecord: Student }): React.JSX.Element {
const { t } = useTranslation(); const { t } = useTranslation();
@@ -29,7 +25,7 @@ export default function SampleTitleCard({ studentRecord }: { studentRecord: Stud
> >
<Avatar <Avatar
variant="rounded" variant="rounded"
src={getImageUrlForStudent(studentRecord)} src={getImageUrlFromFile(studentRecord.collectionId, studentRecord.id, studentRecord.avatar)}
sx={{ '--Avatar-size': '64px' }} sx={{ '--Avatar-size': '64px' }}
> >
{t('empty')} {t('empty')}

View File

@@ -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]);

View File

@@ -9,13 +9,9 @@ import Typography from '@mui/material/Typography';
import { CaretDown as CaretDownIcon } from '@phosphor-icons/react/dist/ssr/CaretDown'; import { CaretDown as CaretDownIcon } from '@phosphor-icons/react/dist/ssr/CaretDown';
import { CheckCircle as CheckCircleIcon } from '@phosphor-icons/react/dist/ssr/CheckCircle'; import { CheckCircle as CheckCircleIcon } from '@phosphor-icons/react/dist/ssr/CheckCircle';
import { useTranslation } from 'react-i18next'; import { useTranslation } from 'react-i18next';
import { Teacher } from '@/components/dashboard/teacher/type.d';
// import type { CrCategory } from '@/components/dashboard/cr/categories/type'; import getImageUrlFromFile from '@/lib/get-image-url-from-file.ts';
import type { Teacher } from '@/components/dashboard/teacher/type.d';
function getImageUrlFrRecord(teacher: Teacher): string {
return `http://127.0.0.1:8090/api/files/${teacher.collectionId}/${teacher.id}/${teacher.avatar}`;
}
export default function SampleTitleCard({ teacherRecord }: { teacherRecord: Teacher }): React.JSX.Element { export default function SampleTitleCard({ teacherRecord }: { teacherRecord: Teacher }): React.JSX.Element {
const { t } = useTranslation(); const { t } = useTranslation();
@@ -29,7 +25,7 @@ export default function SampleTitleCard({ teacherRecord }: { teacherRecord: Teac
> >
<Avatar <Avatar
variant="rounded" variant="rounded"
src={getImageUrlFrRecord(teacherRecord)} src={getImageUrlFromFile(teacherRecord.collectionId, teacherRecord.id, teacherRecord.avatar)}
sx={{ '--Avatar-size': '64px' }} sx={{ '--Avatar-size': '64px' }}
> >
{t('empty')} {t('empty')}

View File

@@ -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);
@@ -96,7 +96,7 @@ export default function Page({ searchParams }: PageProps): React.JSX.Element {
}, [currentPage, rowsPerPage, listOption]); }, [currentPage, rowsPerPage, listOption]);
React.useEffect(() => { React.useEffect(() => {
let tempFilter = []; const tempFilter = [];
let tempSortDir = ''; let tempSortDir = '';
if (state) { if (state) {
@@ -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]);

View File

@@ -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,

View File

@@ -9,13 +9,9 @@ import Typography from '@mui/material/Typography';
import { CaretDown as CaretDownIcon } from '@phosphor-icons/react/dist/ssr/CaretDown'; import { CaretDown as CaretDownIcon } from '@phosphor-icons/react/dist/ssr/CaretDown';
import { CheckCircle as CheckCircleIcon } from '@phosphor-icons/react/dist/ssr/CheckCircle'; import { CheckCircle as CheckCircleIcon } from '@phosphor-icons/react/dist/ssr/CheckCircle';
import { useTranslation } from 'react-i18next'; import { useTranslation } from 'react-i18next';
import { Teacher } from '@/components/dashboard/teacher/type.d';
// import type { CrCategory } from '@/components/dashboard/cr/categories/type'; import getImageUrlFromFile from '@/lib/get-image-url-from-file.ts';
import type { Teacher } from '@/components/dashboard/teacher/type.d';
function getImageUrlFrRecord(teacher: Teacher): string {
return `http://127.0.0.1:8090/api/files/${teacher.collectionId}/${teacher.id}/${teacher.avatar}`;
}
export default function SampleTitleCard({ teacherRecord }: { teacherRecord: Teacher }): React.JSX.Element { export default function SampleTitleCard({ teacherRecord }: { teacherRecord: Teacher }): React.JSX.Element {
const { t } = useTranslation(); const { t } = useTranslation();
@@ -29,7 +25,7 @@ export default function SampleTitleCard({ teacherRecord }: { teacherRecord: Teac
> >
<Avatar <Avatar
variant="rounded" variant="rounded"
src={getImageUrlFrRecord(teacherRecord)} src={getImageUrlFromFile(teacherRecord.collectionId, teacherRecord.id, teacherRecord.avatar)}
sx={{ '--Avatar-size': '64px' }} sx={{ '--Avatar-size': '64px' }}
> >
{t('empty')} {t('empty')}

View File

@@ -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

View File

@@ -1,5 +1,5 @@
import { dayjs } from '@/lib/dayjs'; import { dayjs } from '@/lib/dayjs';
import { LessonCategory } from '@/components/dashboard/lesson_category/type'; import type { LessonCategory } from '@/components/dashboard/lesson_category/type';
// import type { LessonCategory } from '@/components/dashboard/lp_categories/type'; // import type { LessonCategory } from '@/components/dashboard/lp_categories/type';

View File

@@ -34,6 +34,7 @@ import { useTranslation } from 'react-i18next';
import { paths } from '@/paths'; import { paths } from '@/paths';
import { dayjs } from '@/lib/dayjs'; import { dayjs } from '@/lib/dayjs';
import { logger } from '@/lib/default-logger'; import { logger } from '@/lib/default-logger';
import getImageUrlFromFile from '@/lib/get-image-url-from-file.ts';
import { pb } from '@/lib/pb'; import { pb } from '@/lib/pb';
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';
@@ -128,7 +129,11 @@ export default function Page(): React.JSX.Element {
sx={{ alignItems: 'center', flex: '1 1 auto' }} sx={{ alignItems: 'center', flex: '1 1 auto' }}
> >
<Avatar <Avatar
src={`http://127.0.0.1:8090/api/files/${showLessonCategory.collectionId}/${showLessonCategory.id}/${showLessonCategory.cat_image}`} src={getImageUrlFromFile(
showLessonCategory.collectionId,
showLessonCategory.id,
showLessonCategory.image
)}
sx={{ '--Avatar-size': '64px' }} sx={{ '--Avatar-size': '64px' }}
variant="rounded" variant="rounded"
> >
@@ -140,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
@@ -216,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: (

View File

@@ -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 { OAuthProvider } from '../OAuthProvider'; import { oAuthProviders } from '../o-auth-providers.tsx';
import { oAuthProviders } from '../oAuthProviders'; import type { OAuthProvider } from '../OAuthProvider';
// interface OAuthProvider { // interface OAuthProvider {
// id: 'google' | 'discord' | 'github'; // id: 'google' | 'discord' | 'github';
@@ -146,7 +146,7 @@ export function SignInForm(): React.JSX.Element {
<Stack spacing={3}> <Stack spacing={3}>
<Stack <Stack
spacing={2} spacing={2}
direction={'row'} direction="row"
> >
{oAuthProviders.map( {oAuthProviders.map(
(provider): React.JSX.Element => ( (provider): React.JSX.Element => (

View File

@@ -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();
@@ -195,7 +195,7 @@ export function SignUpForm(): React.JSX.Element {
<InputLabel>{t('email-address')}:</InputLabel> <InputLabel>{t('email-address')}:</InputLabel>
<OutlinedInput <OutlinedInput
{...field} {...field}
placeholder={t('e.g.') + ' name@example.com'} placeholder={`${t('e.g.')} name@example.com`}
type="email" type="email"
disabled={isPending} disabled={isPending}
/> />

View File

@@ -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>

View File

@@ -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>
); );
} }

View File

@@ -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>

View File

@@ -1,6 +1,6 @@
import { dayjs } from '@/lib/dayjs'; import { dayjs } from '@/lib/dayjs';
import { CreateForm, LessonCategory } from './type'; import type { CreateForm, LessonCategory } from './type';
// import type { CreateForm, LessonCategory } from '../lp_categories/type'; // import type { CreateForm, LessonCategory } from '../lp_categories/type';

View File

@@ -26,7 +26,7 @@ import { Option } from '@/components/core/option';
// import { LessonCategory } from '../lp_categories/type'; // import { LessonCategory } from '../lp_categories/type';
import { useLessonCategoriesSelection } from './lesson-categories-selection-context'; import { useLessonCategoriesSelection } from './lesson-categories-selection-context';
import { LessonCategory } from './type'; import type { LessonCategory } from './type';
export interface Filters { export interface Filters {
email?: string; email?: string;

View File

@@ -6,7 +6,7 @@ import * as React from 'react';
import { useSelection } from '@/hooks/use-selection'; import { useSelection } from '@/hooks/use-selection';
import type { Selection } from '@/hooks/use-selection'; import type { Selection } from '@/hooks/use-selection';
import { LessonCategory } from './type'; import type { LessonCategory } from './type';
// import type { LessonCategory } from '../lp_categories/type'; // import type { LessonCategory } from '../lp_categories/type';

View File

@@ -99,7 +99,7 @@ function columns(handleDeleteClick: (testId: string) => void): ColumnDef<LessonC
}, },
{ {
formatter: (row): React.JSX.Element => { formatter: (row): React.JSX.Element => {
// eslint-disable-next-line react-hooks/rules-of-hooks
const mapping = { const mapping = {
active: { active: {

View File

@@ -1,6 +1,7 @@
import { dayjs } from '@/lib/dayjs'; import { dayjs } from '@/lib/dayjs';
import { CrCategory, CreateFormProps } from './type'; import type { CrCategory} from './type';
import { CreateFormProps } from './type';
export const defaultCrCategory: CrCategory = { export const defaultCrCategory: CrCategory = {
isEmpty: false, isEmpty: false,

View File

@@ -6,7 +6,7 @@ import * as React from 'react';
import { useSelection } from '@/hooks/use-selection'; import { useSelection } from '@/hooks/use-selection';
import type { Selection } from '@/hooks/use-selection'; import type { Selection } from '@/hooks/use-selection';
import { CrCategory } from './type'; import type { CrCategory } from './type';
function noop(): void { function noop(): void {
return undefined; return undefined;

View File

@@ -97,7 +97,7 @@ function columns(handleDeleteClick: (testId: string) => void): ColumnDef<CrCateg
}, },
{ {
formatter: (row): React.JSX.Element => { formatter: (row): React.JSX.Element => {
// eslint-disable-next-line react-hooks/rules-of-hooks
const mapping = { const mapping = {
active: { active: {

View File

@@ -1,6 +1,7 @@
import { dayjs } from '@/lib/dayjs'; import { dayjs } from '@/lib/dayjs';
import { CreateFormProps, CrQuestion } from './type'; import type { CrQuestion } from './type';
import { CreateFormProps } from './type';
export const defaultCrQuestion: CrQuestion = { export const defaultCrQuestion: CrQuestion = {
isEmpty: false, isEmpty: false,

View File

@@ -24,7 +24,7 @@ import { FilterButton, FilterPopover, useFilterContext } from '@/components/core
import { Option } from '@/components/core/option'; import { Option } from '@/components/core/option';
import { useCrQuestionsSelection } from './cr-questions-selection-context'; import { useCrQuestionsSelection } from './cr-questions-selection-context';
import { CrQuestion } from './type'; import type { CrQuestion } from './type';
export interface Filters { export interface Filters {
email?: string; email?: string;

View File

@@ -97,7 +97,7 @@ function columns(handleDeleteClick: (testId: string) => void): ColumnDef<CrQuest
}, },
{ {
formatter: (row): React.JSX.Element => { formatter: (row): React.JSX.Element => {
// eslint-disable-next-line react-hooks/rules-of-hooks
const mapping = { const mapping = {
active: { active: {

View File

@@ -89,7 +89,7 @@ function columns(handleDeleteClick: (testId: string) => void): ColumnDef<Custome
{ {
formatter: (row): React.JSX.Element => { formatter: (row): React.JSX.Element => {
// eslint-disable-next-line react-hooks/rules-of-hooks
const mapping = { const mapping = {
active: { active: {

View File

@@ -81,7 +81,7 @@ function ErrorDisplay({ message, code, details, severity = 'error' }: PropsError
{formattedMessage} {formattedMessage}
</Typography> </Typography>
{details && <ErrorDetails details={details} />} {details ? <ErrorDetails details={details} /> : null}
</Alert> </Alert>
</Box> </Box>
); );

View File

@@ -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>
); );

View File

@@ -133,7 +133,7 @@ export function NotificationsPopover({
<Typography <Typography
color="lightgray" color="lightgray"
variant={'subtitle2'} variant="subtitle2"
> >
{t('list-is-empty')} {t('list-is-empty')}
</Typography> </Typography>

View File

@@ -1,6 +1,9 @@
'use client'; 'use client';
import { 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[];

View File

@@ -2,7 +2,11 @@
import * as React from 'react'; import * as React from 'react';
import { useRouter } from 'next/navigation'; import { useRouter } from 'next/navigation';
import { LoadingButton } from '@mui/lab';
import { Button } from '@mui/material';
import MenuItem from '@mui/material/MenuItem'; import MenuItem from '@mui/material/MenuItem';
import { SignOut as SignOutIcon } from '@phosphor-icons/react/dist/ssr/SignOut';
import { useTranslation } from 'react-i18next';
import { authClient } from '@/lib/auth/custom/client'; import { authClient } from '@/lib/auth/custom/client';
import { logger } from '@/lib/default-logger'; import { logger } from '@/lib/default-logger';
@@ -10,39 +14,44 @@ import { useUser } from '@/hooks/use-user';
import { toast } from '@/components/core/toaster'; import { toast } from '@/components/core/toaster';
export function CustomSignOut(): React.JSX.Element { export function CustomSignOut(): React.JSX.Element {
const { t } = useTranslation('sign_in');
const { checkSession } = useUser(); const { checkSession } = useUser();
const [buttonShowLoading, setButtonShowLoading] = React.useState<boolean>(false);
const router = useRouter(); const router = useRouter();
const handleSignOut = React.useCallback(async (): Promise<void> => { const handleSignOut = React.useCallback(async (): Promise<void> => {
setButtonShowLoading(true);
try { try {
const { error } = await authClient.signOut(); const { error } = await authClient.signOut();
if (error) { if (error) {
logger.error('Sign out error', error); logger.error('Sign out error', error);
toast.error('Something went wrong, unable to sign out'); toast.error(t('something-went-wrong-unable-to-sign-out'));
return; return;
} }
// Refresh the auth state // Refresh the auth state
await checkSession?.(); await checkSession?.();
// UserProvider, for this case, will not refresh the router and we need to do it manually // UserProvider, for this case, will not refresh the router and we need to do it manually
router.refresh(); router.refresh();
// After refresh, AuthGuard will handle the redirect // After refresh, AuthGuard will handle the redirect
} catch (err) { } catch (err) {
logger.error('Sign out error', err); logger.error('Sign out error', err);
toast.error('Something went wrong, unable to sign out'); toast.error(t('something-went-wrong-unable-to-sign-out'));
} }
}, [checkSession, router]); }, [checkSession, router, t]);
return ( return (
<MenuItem <LoadingButton
component="div"
onClick={handleSignOut} onClick={handleSignOut}
sx={{ justifyContent: 'center' }} sx={{ width: '100%' }}
variant="text"
disabled={buttonShowLoading}
loading={buttonShowLoading}
startIcon={<SignOutIcon />}
color="secondary"
> >
Sign out {t('sign-out')}
</MenuItem> </LoadingButton>
); );
} }

View File

@@ -16,15 +16,15 @@ import { User as UserIcon } from '@phosphor-icons/react/dist/ssr/User';
import type { User } from '@/types/user'; import type { User } from '@/types/user';
import { config } from '@/config'; import { config } from '@/config';
import { paths } from '@/paths'; import { paths } from '@/paths';
import { authClient } from '@/lib/auth/custom/client';
import { AuthStrategy } from '@/lib/auth/strategy'; import { AuthStrategy } from '@/lib/auth/strategy';
import { logger } from '@/lib/default-logger';
import { Auth0SignOut } from './auth0-sign-out'; import { Auth0SignOut } from './auth0-sign-out';
import { CognitoSignOut } from './cognito-sign-out'; import { CognitoSignOut } from './cognito-sign-out';
import { CustomSignOut } from './custom-sign-out'; import { CustomSignOut } from './custom-sign-out';
import { FirebaseSignOut } from './firebase-sign-out'; import { FirebaseSignOut } from './firebase-sign-out';
import { SupabaseSignOut } from './supabase-sign-out'; import { SupabaseSignOut } from './supabase-sign-out';
import { authClient } from '@/lib/auth/custom/client';
import { logger } from '@/lib/default-logger';
const defaultUser = { const defaultUser = {
id: 'USR-000', id: 'USR-000',
@@ -55,7 +55,8 @@ export function UserPopover({ anchorEl, onClose, open }: UserPopoverProps): Reac
void loadUserMeta(); void loadUserMeta();
}, []); }, []);
if (!userMeta) return <>loading</>; // NOTE: delay when userMeta is null, used for sign-out
if (!userMeta) return <></>;
return ( return (
<Popover <Popover

View File

@@ -14,7 +14,7 @@ import { logger } from '@/lib/default-logger';
import { toast } from '@/components/core/toaster'; import { toast } from '@/components/core/toaster';
import { MarkOneAsRead } from '@/db/Notifications/mark-one-as-read'; import { MarkOneAsRead } from '@/db/Notifications/mark-one-as-read';
import { getUnreadNotificationsByUserId } from '@/db/Notifications/GetUnreadNotificationsByUserId'; import { getUnreadNotificationsByUserId } from '@/db/Notifications/GetUnreadNotificationsByUserId';
import { Notification } from '@/db/Notifications/type'; import type { Notification } from '@/db/Notifications/type';
import { useUser } from '@/hooks/use-user'; import { useUser } from '@/hooks/use-user';
export function NotificationsButton(): React.JSX.Element { export function NotificationsButton(): React.JSX.Element {

View File

@@ -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

View File

@@ -66,6 +66,7 @@ export function SideNav({ color = 'evident', items = [] }: SideNavProps): React.
spacing={2} spacing={2}
sx={{ p: 2 }} sx={{ p: 2 }}
> >
{/* NOTE: hide logo
<div> <div>
<Box <Box
component={RouterLink} component={RouterLink}
@@ -79,6 +80,7 @@ export function SideNav({ color = 'evident', items = [] }: SideNavProps): React.
/> />
</Box> </Box>
</div> </div>
*/}
<WorkspacesSwitch /> <WorkspacesSwitch />
</Stack> </Stack>
<Box <Box

View File

@@ -1,6 +1,6 @@
import { dayjs } from '@/lib/dayjs'; import { dayjs } from '@/lib/dayjs';
import { CreateForm, LessonCategory } from './type'; import type { CreateForm, LessonCategory } from './type';
// import type { CreateForm, LessonCategory } from '../lp_categories/type'; // import type { CreateForm, LessonCategory } from '../lp_categories/type';

View File

@@ -26,7 +26,7 @@ import { Option } from '@/components/core/option';
// import { LessonCategory } from '../lp_categories/type'; // import { LessonCategory } from '../lp_categories/type';
import { useLessonCategoriesSelection } from './lesson-categories-selection-context'; import { useLessonCategoriesSelection } from './lesson-categories-selection-context';
import { LessonCategory } from './type'; import type { LessonCategory } from './type';
export interface Filters { export interface Filters {
email?: string; email?: string;

View File

@@ -6,7 +6,7 @@ import * as React from 'react';
import { useSelection } from '@/hooks/use-selection'; import { useSelection } from '@/hooks/use-selection';
import type { Selection } from '@/hooks/use-selection'; import type { Selection } from '@/hooks/use-selection';
import { LessonCategory } from './type'; import type { LessonCategory } from './type';
// import type { LessonCategory } from '../lp_categories/type'; // import type { LessonCategory } from '../lp_categories/type';

View File

@@ -99,7 +99,7 @@ function columns(handleDeleteClick: (testId: string) => void): ColumnDef<LessonC
}, },
{ {
formatter: (row): React.JSX.Element => { formatter: (row): React.JSX.Element => {
// eslint-disable-next-line react-hooks/rules-of-hooks
const mapping = { const mapping = {
active: { active: {

View File

@@ -37,7 +37,7 @@ import { pb } from '@/lib/pb';
import { toast } from '@/components/core/toaster'; import { toast } from '@/components/core/toaster';
import { LessonTypeCreateFormDefault } from './_constants'; import { LessonTypeCreateFormDefault } from './_constants';
import { CreateForm } from './lesson-type'; import type { CreateForm } from './lesson-type';
const schema = zod.object({ const schema = zod.object({
name: zod.string().min(1, 'Name is required').max(255), name: zod.string().min(1, 'Name is required').max(255),

View File

@@ -5,7 +5,7 @@ import * as React from 'react';
import { useSelection } from '@/hooks/use-selection'; import { useSelection } from '@/hooks/use-selection';
import type { Selection } from '@/hooks/use-selection'; import type { Selection } from '@/hooks/use-selection';
import { LessonType } from './lesson-type'; import type { LessonType } from './lesson-type';
// import { LessonType } from './ILessonType'; // import { LessonType } from './ILessonType';

View File

@@ -1,6 +1,7 @@
import { dayjs } from '@/lib/dayjs'; import { dayjs } from '@/lib/dayjs';
import { CreateFormProps, LpCategory } from './type'; import type { LpCategory } from './type';
import { CreateFormProps } from './type';
export const defaultLpCategory: LpCategory = { export const defaultLpCategory: LpCategory = {
isEmpty: false, isEmpty: false,

View File

@@ -24,7 +24,7 @@ import { FilterButton, FilterPopover, useFilterContext } from '@/components/core
import { Option } from '@/components/core/option'; import { Option } from '@/components/core/option';
import { useLpCategoriesSelection } from './lp-categories-selection-context'; import { useLpCategoriesSelection } from './lp-categories-selection-context';
import { LpCategory } from './type'; import type { LpCategory } from './type';
export interface Filters { export interface Filters {
email?: string; email?: string;

View File

@@ -6,7 +6,7 @@ import * as React from 'react';
import { useSelection } from '@/hooks/use-selection'; import { useSelection } from '@/hooks/use-selection';
import type { Selection } from '@/hooks/use-selection'; import type { Selection } from '@/hooks/use-selection';
import { LpCategory } from './type'; import type { LpCategory } from './type';
function noop(): void { function noop(): void {
return undefined; return undefined;

View File

@@ -97,7 +97,7 @@ function columns(handleDeleteClick: (testId: string) => void): ColumnDef<LpCateg
}, },
{ {
formatter: (row): React.JSX.Element => { formatter: (row): React.JSX.Element => {
// eslint-disable-next-line react-hooks/rules-of-hooks
const mapping = { const mapping = {
active: { active: {

View File

@@ -24,7 +24,7 @@ import { FilterButton, FilterPopover, useFilterContext } from '@/components/core
import { Option } from '@/components/core/option'; import { Option } from '@/components/core/option';
import { useLpCategoriesSelection } from './lp-categories-selection-context'; import { useLpCategoriesSelection } from './lp-categories-selection-context';
import { LpCategory } from './type'; import type { LpCategory } from './type';
export interface Filters { export interface Filters {
email?: string; email?: string;

View File

@@ -6,7 +6,7 @@ import * as React from 'react';
import { useSelection } from '@/hooks/use-selection'; import { useSelection } from '@/hooks/use-selection';
import type { Selection } from '@/hooks/use-selection'; import type { Selection } from '@/hooks/use-selection';
import { LpCategory } from './type'; import type { LpCategory } from './type';
function noop(): void { function noop(): void {
return undefined; return undefined;

View File

@@ -97,7 +97,7 @@ function columns(handleDeleteClick: (testId: string) => void): ColumnDef<LpCateg
}, },
{ {
formatter: (row): React.JSX.Element => { formatter: (row): React.JSX.Element => {
// eslint-disable-next-line react-hooks/rules-of-hooks
const mapping = { const mapping = {
active: { active: {

View File

@@ -1,6 +1,7 @@
import { dayjs } from '@/lib/dayjs'; import { dayjs } from '@/lib/dayjs';
import { CreateFormProps, LpQuestion } from './type'; import type { LpQuestion } from './type';
import { CreateFormProps } from './type';
export const defaultLpQuestion: LpQuestion = { export const defaultLpQuestion: LpQuestion = {
isEmpty: false, isEmpty: false,

View File

@@ -24,7 +24,7 @@ import { FilterButton, FilterPopover, useFilterContext } from '@/components/core
import { Option } from '@/components/core/option'; import { Option } from '@/components/core/option';
import { useLpQuestionsSelection } from './lp-questions-selection-context'; import { useLpQuestionsSelection } from './lp-questions-selection-context';
import { LpQuestion } from './type'; import type { LpQuestion } from './type';
export interface Filters { export interface Filters {
email?: string; email?: string;

View File

@@ -97,7 +97,7 @@ function columns(handleDeleteClick: (testId: string) => void): ColumnDef<LpQuest
}, },
{ {
formatter: (row): React.JSX.Element => { formatter: (row): React.JSX.Element => {
// eslint-disable-next-line react-hooks/rules-of-hooks
const mapping = { const mapping = {
active: { active: {

View File

@@ -1,6 +1,7 @@
import { dayjs } from '@/lib/dayjs'; import { dayjs } from '@/lib/dayjs';
import { CreateFormProps, MfCategory } from './type'; import type { MfCategory } from './type';
import { CreateFormProps } from './type';
export const defaultMfCategory: MfCategory = { export const defaultMfCategory: MfCategory = {
isEmpty: false, isEmpty: false,

View File

@@ -24,7 +24,7 @@ import { FilterButton, FilterPopover, useFilterContext } from '@/components/core
import { Option } from '@/components/core/option'; import { Option } from '@/components/core/option';
import { useMfCategoriesSelection } from './mf-categories-selection-context'; import { useMfCategoriesSelection } from './mf-categories-selection-context';
import { MfCategory } from './type'; import type { MfCategory } from './type';
export interface Filters { export interface Filters {
email?: string; email?: string;

View File

@@ -6,7 +6,7 @@ import * as React from 'react';
import { useSelection } from '@/hooks/use-selection'; import { useSelection } from '@/hooks/use-selection';
import type { Selection } from '@/hooks/use-selection'; import type { Selection } from '@/hooks/use-selection';
import { MfCategory } from './type'; import type { MfCategory } from './type';
function noop(): void { function noop(): void {
return undefined; return undefined;

Some files were not shown because too many files have changed in this diff Show More