mirror of
https://github.com/lucasrcsantana/story-generator.git
synced 2025-12-17 05:47:52 +00:00
feat: adiciona página de configurações do aluno
- Cria componente StudentSettingsPage - Adiciona rota de configurações - Implementa utils para classes condicionais - Atualiza navegação no dashboard do aluno
This commit is contained in:
parent
1e181785b4
commit
6e7c85e853
301
package-lock.json
generated
301
package-lock.json
generated
@ -8,12 +8,15 @@
|
||||
"name": "vite-react-typescript-starter",
|
||||
"version": "0.0.0",
|
||||
"dependencies": {
|
||||
"@radix-ui/react-tabs": "^1.1.2",
|
||||
"@supabase/supabase-js": "^2.39.7",
|
||||
"clsx": "^2.1.1",
|
||||
"lucide-react": "^0.344.0",
|
||||
"react": "^18.3.1",
|
||||
"react-dom": "^18.3.1",
|
||||
"react-router-dom": "^6.28.0",
|
||||
"resend": "^3.2.0"
|
||||
"resend": "^3.2.0",
|
||||
"tailwind-merge": "^2.5.5"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@eslint/js": "^9.9.1",
|
||||
@ -1002,6 +1005,275 @@
|
||||
"node": ">=14"
|
||||
}
|
||||
},
|
||||
"node_modules/@radix-ui/primitive": {
|
||||
"version": "1.1.1",
|
||||
"resolved": "https://registry.npmjs.org/@radix-ui/primitive/-/primitive-1.1.1.tgz",
|
||||
"integrity": "sha512-SJ31y+Q/zAyShtXJc8x83i9TYdbAfHZ++tUZnvjJJqFjzsdUnKsxPL6IEtBlxKkU7yzer//GQtZSV4GbldL3YA==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/@radix-ui/react-collection": {
|
||||
"version": "1.1.1",
|
||||
"resolved": "https://registry.npmjs.org/@radix-ui/react-collection/-/react-collection-1.1.1.tgz",
|
||||
"integrity": "sha512-LwT3pSho9Dljg+wY2KN2mrrh6y3qELfftINERIzBUO9e0N+t0oMTyn3k9iv+ZqgrwGkRnLpNJrsMv9BZlt2yuA==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@radix-ui/react-compose-refs": "1.1.1",
|
||||
"@radix-ui/react-context": "1.1.1",
|
||||
"@radix-ui/react-primitive": "2.0.1",
|
||||
"@radix-ui/react-slot": "1.1.1"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@types/react": "*",
|
||||
"@types/react-dom": "*",
|
||||
"react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc",
|
||||
"react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"@types/react": {
|
||||
"optional": true
|
||||
},
|
||||
"@types/react-dom": {
|
||||
"optional": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/@radix-ui/react-compose-refs": {
|
||||
"version": "1.1.1",
|
||||
"resolved": "https://registry.npmjs.org/@radix-ui/react-compose-refs/-/react-compose-refs-1.1.1.tgz",
|
||||
"integrity": "sha512-Y9VzoRDSJtgFMUCoiZBDVo084VQ5hfpXxVE+NgkdNsjiDBByiImMZKKhxMwCbdHvhlENG6a833CbFkOQvTricw==",
|
||||
"license": "MIT",
|
||||
"peerDependencies": {
|
||||
"@types/react": "*",
|
||||
"react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"@types/react": {
|
||||
"optional": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/@radix-ui/react-context": {
|
||||
"version": "1.1.1",
|
||||
"resolved": "https://registry.npmjs.org/@radix-ui/react-context/-/react-context-1.1.1.tgz",
|
||||
"integrity": "sha512-UASk9zi+crv9WteK/NU4PLvOoL3OuE6BWVKNF6hPRBtYBDXQ2u5iu3O59zUlJiTVvkyuycnqrztsHVJwcK9K+Q==",
|
||||
"license": "MIT",
|
||||
"peerDependencies": {
|
||||
"@types/react": "*",
|
||||
"react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"@types/react": {
|
||||
"optional": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/@radix-ui/react-direction": {
|
||||
"version": "1.1.0",
|
||||
"resolved": "https://registry.npmjs.org/@radix-ui/react-direction/-/react-direction-1.1.0.tgz",
|
||||
"integrity": "sha512-BUuBvgThEiAXh2DWu93XsT+a3aWrGqolGlqqw5VU1kG7p/ZH2cuDlM1sRLNnY3QcBS69UIz2mcKhMxDsdewhjg==",
|
||||
"license": "MIT",
|
||||
"peerDependencies": {
|
||||
"@types/react": "*",
|
||||
"react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"@types/react": {
|
||||
"optional": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/@radix-ui/react-id": {
|
||||
"version": "1.1.0",
|
||||
"resolved": "https://registry.npmjs.org/@radix-ui/react-id/-/react-id-1.1.0.tgz",
|
||||
"integrity": "sha512-EJUrI8yYh7WOjNOqpoJaf1jlFIH2LvtgAl+YcFqNCa+4hj64ZXmPkAKOFs/ukjz3byN6bdb/AVUqHkI8/uWWMA==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@radix-ui/react-use-layout-effect": "1.1.0"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@types/react": "*",
|
||||
"react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"@types/react": {
|
||||
"optional": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/@radix-ui/react-presence": {
|
||||
"version": "1.1.2",
|
||||
"resolved": "https://registry.npmjs.org/@radix-ui/react-presence/-/react-presence-1.1.2.tgz",
|
||||
"integrity": "sha512-18TFr80t5EVgL9x1SwF/YGtfG+l0BS0PRAlCWBDoBEiDQjeKgnNZRVJp/oVBl24sr3Gbfwc/Qpj4OcWTQMsAEg==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@radix-ui/react-compose-refs": "1.1.1",
|
||||
"@radix-ui/react-use-layout-effect": "1.1.0"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@types/react": "*",
|
||||
"@types/react-dom": "*",
|
||||
"react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc",
|
||||
"react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"@types/react": {
|
||||
"optional": true
|
||||
},
|
||||
"@types/react-dom": {
|
||||
"optional": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/@radix-ui/react-primitive": {
|
||||
"version": "2.0.1",
|
||||
"resolved": "https://registry.npmjs.org/@radix-ui/react-primitive/-/react-primitive-2.0.1.tgz",
|
||||
"integrity": "sha512-sHCWTtxwNn3L3fH8qAfnF3WbUZycW93SM1j3NFDzXBiz8D6F5UTTy8G1+WFEaiCdvCVRJWj6N2R4Xq6HdiHmDg==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@radix-ui/react-slot": "1.1.1"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@types/react": "*",
|
||||
"@types/react-dom": "*",
|
||||
"react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc",
|
||||
"react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"@types/react": {
|
||||
"optional": true
|
||||
},
|
||||
"@types/react-dom": {
|
||||
"optional": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/@radix-ui/react-roving-focus": {
|
||||
"version": "1.1.1",
|
||||
"resolved": "https://registry.npmjs.org/@radix-ui/react-roving-focus/-/react-roving-focus-1.1.1.tgz",
|
||||
"integrity": "sha512-QE1RoxPGJ/Nm8Qmk0PxP8ojmoaS67i0s7hVssS7KuI2FQoc/uzVlZsqKfQvxPE6D8hICCPHJ4D88zNhT3OOmkw==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@radix-ui/primitive": "1.1.1",
|
||||
"@radix-ui/react-collection": "1.1.1",
|
||||
"@radix-ui/react-compose-refs": "1.1.1",
|
||||
"@radix-ui/react-context": "1.1.1",
|
||||
"@radix-ui/react-direction": "1.1.0",
|
||||
"@radix-ui/react-id": "1.1.0",
|
||||
"@radix-ui/react-primitive": "2.0.1",
|
||||
"@radix-ui/react-use-callback-ref": "1.1.0",
|
||||
"@radix-ui/react-use-controllable-state": "1.1.0"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@types/react": "*",
|
||||
"@types/react-dom": "*",
|
||||
"react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc",
|
||||
"react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"@types/react": {
|
||||
"optional": true
|
||||
},
|
||||
"@types/react-dom": {
|
||||
"optional": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/@radix-ui/react-slot": {
|
||||
"version": "1.1.1",
|
||||
"resolved": "https://registry.npmjs.org/@radix-ui/react-slot/-/react-slot-1.1.1.tgz",
|
||||
"integrity": "sha512-RApLLOcINYJA+dMVbOju7MYv1Mb2EBp2nH4HdDzXTSyaR5optlm6Otrz1euW3HbdOR8UmmFK06TD+A9frYWv+g==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@radix-ui/react-compose-refs": "1.1.1"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@types/react": "*",
|
||||
"react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"@types/react": {
|
||||
"optional": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/@radix-ui/react-tabs": {
|
||||
"version": "1.1.2",
|
||||
"resolved": "https://registry.npmjs.org/@radix-ui/react-tabs/-/react-tabs-1.1.2.tgz",
|
||||
"integrity": "sha512-9u/tQJMcC2aGq7KXpGivMm1mgq7oRJKXphDwdypPd/j21j/2znamPU8WkXgnhUaTrSFNIt8XhOyCAupg8/GbwQ==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@radix-ui/primitive": "1.1.1",
|
||||
"@radix-ui/react-context": "1.1.1",
|
||||
"@radix-ui/react-direction": "1.1.0",
|
||||
"@radix-ui/react-id": "1.1.0",
|
||||
"@radix-ui/react-presence": "1.1.2",
|
||||
"@radix-ui/react-primitive": "2.0.1",
|
||||
"@radix-ui/react-roving-focus": "1.1.1",
|
||||
"@radix-ui/react-use-controllable-state": "1.1.0"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@types/react": "*",
|
||||
"@types/react-dom": "*",
|
||||
"react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc",
|
||||
"react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"@types/react": {
|
||||
"optional": true
|
||||
},
|
||||
"@types/react-dom": {
|
||||
"optional": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/@radix-ui/react-use-callback-ref": {
|
||||
"version": "1.1.0",
|
||||
"resolved": "https://registry.npmjs.org/@radix-ui/react-use-callback-ref/-/react-use-callback-ref-1.1.0.tgz",
|
||||
"integrity": "sha512-CasTfvsy+frcFkbXtSJ2Zu9JHpN8TYKxkgJGWbjiZhFivxaeW7rMeZt7QELGVLaYVfFMsKHjb7Ak0nMEe+2Vfw==",
|
||||
"license": "MIT",
|
||||
"peerDependencies": {
|
||||
"@types/react": "*",
|
||||
"react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"@types/react": {
|
||||
"optional": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/@radix-ui/react-use-controllable-state": {
|
||||
"version": "1.1.0",
|
||||
"resolved": "https://registry.npmjs.org/@radix-ui/react-use-controllable-state/-/react-use-controllable-state-1.1.0.tgz",
|
||||
"integrity": "sha512-MtfMVJiSr2NjzS0Aa90NPTnvTSg6C/JLCV7ma0W6+OMV78vd8OyRpID+Ng9LxzsPbLeuBnWBA1Nq30AtBIDChw==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@radix-ui/react-use-callback-ref": "1.1.0"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@types/react": "*",
|
||||
"react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"@types/react": {
|
||||
"optional": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/@radix-ui/react-use-layout-effect": {
|
||||
"version": "1.1.0",
|
||||
"resolved": "https://registry.npmjs.org/@radix-ui/react-use-layout-effect/-/react-use-layout-effect-1.1.0.tgz",
|
||||
"integrity": "sha512-+FPE0rOdziWSrH9athwI1R0HDVbWlEhd+FR+aSDk4uWGmSJ9Z54sdZVDQPZAinJhJXwfT+qnj969mCsT2gfm5w==",
|
||||
"license": "MIT",
|
||||
"peerDependencies": {
|
||||
"@types/react": "*",
|
||||
"react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"@types/react": {
|
||||
"optional": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/@react-email/render": {
|
||||
"version": "0.0.16",
|
||||
"resolved": "https://registry.npmjs.org/@react-email/render/-/render-0.0.16.tgz",
|
||||
@ -1396,13 +1668,13 @@
|
||||
"version": "15.7.13",
|
||||
"resolved": "https://registry.npmjs.org/@types/prop-types/-/prop-types-15.7.13.tgz",
|
||||
"integrity": "sha512-hCZTSvwbzWGvhqxp/RqVqwU999pBf2vp7hzIjiYOsl8wqOmUxkQ6ddw1cV3l8811+kdUFus/q4d1Y3E3SyEifA==",
|
||||
"dev": true
|
||||
"devOptional": true
|
||||
},
|
||||
"node_modules/@types/react": {
|
||||
"version": "18.3.17",
|
||||
"resolved": "https://registry.npmjs.org/@types/react/-/react-18.3.17.tgz",
|
||||
"integrity": "sha512-opAQ5no6LqJNo9TqnxBKsgnkIYHozW9KSTlFVoSUJYh1Fl/sswkEoqIugRSm7tbh6pABtYjGAjW+GOS23j8qbw==",
|
||||
"dev": true,
|
||||
"devOptional": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@types/prop-types": "*",
|
||||
@ -1413,7 +1685,7 @@
|
||||
"version": "18.3.5",
|
||||
"resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-18.3.5.tgz",
|
||||
"integrity": "sha512-P4t6saawp+b/dFrUr2cvkVsfvPguwsxtH6dNIYRllMsefqFzkZk5UIjzyDOv5g1dXIPdG4Sp1yCR4Z6RCUsG/Q==",
|
||||
"dev": true,
|
||||
"devOptional": true,
|
||||
"license": "MIT",
|
||||
"peerDependencies": {
|
||||
"@types/react": "^18.0.0"
|
||||
@ -1974,6 +2246,15 @@
|
||||
"node": ">= 6"
|
||||
}
|
||||
},
|
||||
"node_modules/clsx": {
|
||||
"version": "2.1.1",
|
||||
"resolved": "https://registry.npmjs.org/clsx/-/clsx-2.1.1.tgz",
|
||||
"integrity": "sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=6"
|
||||
}
|
||||
},
|
||||
"node_modules/color-convert": {
|
||||
"version": "1.9.3",
|
||||
"resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz",
|
||||
@ -2049,7 +2330,7 @@
|
||||
"version": "3.1.3",
|
||||
"resolved": "https://registry.npmjs.org/csstype/-/csstype-3.1.3.tgz",
|
||||
"integrity": "sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==",
|
||||
"dev": true
|
||||
"devOptional": true
|
||||
},
|
||||
"node_modules/debug": {
|
||||
"version": "4.3.7",
|
||||
@ -4086,6 +4367,16 @@
|
||||
"url": "https://github.com/sponsors/ljharb"
|
||||
}
|
||||
},
|
||||
"node_modules/tailwind-merge": {
|
||||
"version": "2.5.5",
|
||||
"resolved": "https://registry.npmjs.org/tailwind-merge/-/tailwind-merge-2.5.5.tgz",
|
||||
"integrity": "sha512-0LXunzzAZzo0tEPxV3I297ffKZPlKDrjj7NXphC8V5ak9yHC5zRmxnOe2m/Rd/7ivsOMJe3JZ2JVocoDdQTRBA==",
|
||||
"license": "MIT",
|
||||
"funding": {
|
||||
"type": "github",
|
||||
"url": "https://github.com/sponsors/dcastil"
|
||||
}
|
||||
},
|
||||
"node_modules/tailwindcss": {
|
||||
"version": "3.4.17",
|
||||
"resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-3.4.17.tgz",
|
||||
|
||||
@ -12,12 +12,15 @@
|
||||
"format": "prettier --write \"src/**/*.{ts,tsx}\""
|
||||
},
|
||||
"dependencies": {
|
||||
"@radix-ui/react-tabs": "^1.1.2",
|
||||
"@supabase/supabase-js": "^2.39.7",
|
||||
"clsx": "^2.1.1",
|
||||
"lucide-react": "^0.344.0",
|
||||
"react": "^18.3.1",
|
||||
"react-dom": "^18.3.1",
|
||||
"react-router-dom": "^6.28.0",
|
||||
"resend": "^3.2.0"
|
||||
"resend": "^3.2.0",
|
||||
"tailwind-merge": "^2.5.5"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@eslint/js": "^9.9.1",
|
||||
|
||||
40
src/components/ui/avatar-upload.tsx
Normal file
40
src/components/ui/avatar-upload.tsx
Normal file
@ -0,0 +1,40 @@
|
||||
import React, { useRef, useState } from 'react';
|
||||
import { Camera } from 'lucide-react';
|
||||
|
||||
export function AvatarUpload(): JSX.Element {
|
||||
const [preview, setPreview] = useState<string>();
|
||||
const fileInputRef = useRef<HTMLInputElement>(null);
|
||||
|
||||
const handleFileChange = (event: React.ChangeEvent<HTMLInputElement>) => {
|
||||
const file = event.target.files?.[0];
|
||||
if (file) {
|
||||
const reader = new FileReader();
|
||||
reader.onloadend = () => {
|
||||
setPreview(reader.result as string);
|
||||
};
|
||||
reader.readAsDataURL(file);
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="relative w-24 h-24">
|
||||
<div
|
||||
className="w-full h-full rounded-full bg-gray-100 flex items-center justify-center overflow-hidden border-2 border-gray-200"
|
||||
onClick={() => fileInputRef.current?.click()}
|
||||
>
|
||||
{preview ? (
|
||||
<img src={preview} alt="Avatar" className="w-full h-full object-cover" />
|
||||
) : (
|
||||
<Camera className="h-8 w-8 text-gray-400" />
|
||||
)}
|
||||
</div>
|
||||
<input
|
||||
ref={fileInputRef}
|
||||
type="file"
|
||||
accept="image/*"
|
||||
className="hidden"
|
||||
onChange={handleFileChange}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
27
src/components/ui/date-picker.tsx
Normal file
27
src/components/ui/date-picker.tsx
Normal file
@ -0,0 +1,27 @@
|
||||
import React from 'react';
|
||||
|
||||
interface DatePickerProps {
|
||||
label?: string;
|
||||
name: string;
|
||||
value?: string;
|
||||
onChange?: (date: string) => void;
|
||||
}
|
||||
|
||||
export function DatePicker({ label, name, value, onChange }: DatePickerProps): JSX.Element {
|
||||
return (
|
||||
<div className="space-y-2">
|
||||
{label && (
|
||||
<label className="text-sm font-medium text-gray-700">
|
||||
{label}
|
||||
</label>
|
||||
)}
|
||||
<input
|
||||
type="date"
|
||||
name={name}
|
||||
value={value}
|
||||
onChange={(e) => onChange?.(e.target.value)}
|
||||
className="w-full rounded-md border border-gray-300 px-3 py-2 text-sm focus:outline-none focus:ring-2 focus:ring-purple-500"
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
21
src/components/ui/input.tsx
Normal file
21
src/components/ui/input.tsx
Normal file
@ -0,0 +1,21 @@
|
||||
import React from 'react';
|
||||
|
||||
interface InputProps extends React.InputHTMLAttributes<HTMLInputElement> {
|
||||
label?: string;
|
||||
}
|
||||
|
||||
export function Input({ label, className, ...props }: InputProps): JSX.Element {
|
||||
return (
|
||||
<div className="space-y-2">
|
||||
{label && (
|
||||
<label className="text-sm font-medium text-gray-700">
|
||||
{label}
|
||||
</label>
|
||||
)}
|
||||
<input
|
||||
className={`w-full rounded-md border border-gray-300 px-3 py-2 text-sm focus:outline-none focus:ring-2 focus:ring-purple-500 ${className}`}
|
||||
{...props}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
39
src/components/ui/select.tsx
Normal file
39
src/components/ui/select.tsx
Normal file
@ -0,0 +1,39 @@
|
||||
import React from 'react';
|
||||
|
||||
interface SelectOption {
|
||||
value: string;
|
||||
label: string;
|
||||
}
|
||||
|
||||
interface SelectProps {
|
||||
label?: string;
|
||||
name: string;
|
||||
options: SelectOption[];
|
||||
value?: string;
|
||||
onChange?: (value: string) => void;
|
||||
}
|
||||
|
||||
export function Select({ label, name, options, value, onChange }: SelectProps): JSX.Element {
|
||||
return (
|
||||
<div className="space-y-2">
|
||||
{label && (
|
||||
<label className="text-sm font-medium text-gray-700">
|
||||
{label}
|
||||
</label>
|
||||
)}
|
||||
<select
|
||||
name={name}
|
||||
value={value}
|
||||
onChange={(e) => onChange?.(e.target.value)}
|
||||
className="w-full rounded-md border border-gray-300 px-3 py-2 text-sm focus:outline-none focus:ring-2 focus:ring-purple-500"
|
||||
>
|
||||
<option value="">Selecione...</option>
|
||||
{options.map((option) => (
|
||||
<option key={option.value} value={option.value}>
|
||||
{option.label}
|
||||
</option>
|
||||
))}
|
||||
</select>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
52
src/components/ui/tabs.tsx
Normal file
52
src/components/ui/tabs.tsx
Normal file
@ -0,0 +1,52 @@
|
||||
import * as React from "react"
|
||||
import * as TabsPrimitive from "@radix-ui/react-tabs"
|
||||
import { cn } from "../../lib/utils"
|
||||
|
||||
const Tabs = TabsPrimitive.Root
|
||||
|
||||
const TabsList = React.forwardRef<
|
||||
React.ElementRef<typeof TabsPrimitive.List>,
|
||||
React.ComponentPropsWithoutRef<typeof TabsPrimitive.List>
|
||||
>(({ className, ...props }, ref) => (
|
||||
<TabsPrimitive.List
|
||||
ref={ref}
|
||||
className={cn(
|
||||
"inline-flex h-10 items-center justify-center rounded-lg bg-gray-100 p-1 text-gray-500",
|
||||
className
|
||||
)}
|
||||
{...props}
|
||||
/>
|
||||
))
|
||||
TabsList.displayName = TabsPrimitive.List.displayName
|
||||
|
||||
const TabsTrigger = React.forwardRef<
|
||||
React.ElementRef<typeof TabsPrimitive.Trigger>,
|
||||
React.ComponentPropsWithoutRef<typeof TabsPrimitive.Trigger>
|
||||
>(({ className, ...props }, ref) => (
|
||||
<TabsPrimitive.Trigger
|
||||
ref={ref}
|
||||
className={cn(
|
||||
"inline-flex items-center justify-center whitespace-nowrap rounded-md px-3 py-1.5 text-sm font-medium ring-offset-white transition-all focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-gray-400 focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50 data-[state=active]:bg-white data-[state=active]:text-gray-950 data-[state=active]:shadow-sm",
|
||||
className
|
||||
)}
|
||||
{...props}
|
||||
/>
|
||||
))
|
||||
TabsTrigger.displayName = TabsPrimitive.Trigger.displayName
|
||||
|
||||
const TabsContent = React.forwardRef<
|
||||
React.ElementRef<typeof TabsPrimitive.Content>,
|
||||
React.ComponentPropsWithoutRef<typeof TabsPrimitive.Content>
|
||||
>(({ className, ...props }, ref) => (
|
||||
<TabsPrimitive.Content
|
||||
ref={ref}
|
||||
className={cn(
|
||||
"mt-2 ring-offset-white focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-gray-400 focus-visible:ring-offset-2",
|
||||
className
|
||||
)}
|
||||
{...props}
|
||||
/>
|
||||
))
|
||||
TabsContent.displayName = TabsPrimitive.Content.displayName
|
||||
|
||||
export { Tabs, TabsList, TabsTrigger, TabsContent }
|
||||
6
src/lib/utils.ts
Normal file
6
src/lib/utils.ts
Normal file
@ -0,0 +1,6 @@
|
||||
import { type ClassValue, clsx } from "clsx"
|
||||
import { twMerge } from "tailwind-merge"
|
||||
|
||||
export function cn(...inputs: ClassValue[]) {
|
||||
return twMerge(clsx(inputs))
|
||||
}
|
||||
15
src/pages/student-dashboard/StudentClassPage.tsx
Normal file
15
src/pages/student-dashboard/StudentClassPage.tsx
Normal file
@ -0,0 +1,15 @@
|
||||
import React from 'react';
|
||||
import { useParams } from 'react-router-dom';
|
||||
|
||||
export function StudentClassPage(): JSX.Element {
|
||||
const { classId } = useParams();
|
||||
|
||||
return (
|
||||
<div className="space-y-6">
|
||||
<h1 className="text-2xl font-bold text-gray-900">
|
||||
Turma {classId}
|
||||
</h1>
|
||||
{/* Conteúdo da página da turma será implementado aqui */}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
12
src/pages/student-dashboard/StudentDashboard.tsx
Normal file
12
src/pages/student-dashboard/StudentDashboard.tsx
Normal file
@ -0,0 +1,12 @@
|
||||
import React from 'react';
|
||||
|
||||
export function StudentDashboard(): JSX.Element {
|
||||
return (
|
||||
<div className="space-y-6">
|
||||
<h1 className="text-2xl font-bold text-gray-900">
|
||||
Bem-vindo ao Dashboard
|
||||
</h1>
|
||||
{/* Conteúdo do dashboard será implementado aqui */}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@ -92,7 +92,7 @@ export function StudentDashboardLayout() {
|
||||
className={({ isActive }) =>
|
||||
`flex items-center gap-2 px-4 py-2 rounded-lg text-sm ${
|
||||
isActive
|
||||
? 'bg-purple-50 text-purple-700'
|
||||
? 'bg-purple-50 text-purple-600'
|
||||
: 'text-gray-600 hover:bg-gray-50'
|
||||
}`
|
||||
}
|
||||
|
||||
66
src/pages/student-dashboard/StudentSettingsPage.tsx
Normal file
66
src/pages/student-dashboard/StudentSettingsPage.tsx
Normal file
@ -0,0 +1,66 @@
|
||||
import { Tabs, TabsList, TabsTrigger, TabsContent } from '../../components/ui/tabs';
|
||||
import { Input } from '../../components/ui/input';
|
||||
import { DatePicker } from '../../components/ui/date-picker';
|
||||
import { Select } from '../../components/ui/select';
|
||||
import { AvatarUpload } from '../../components/ui/avatar-upload';
|
||||
|
||||
export function StudentSettingsPage() {
|
||||
return (
|
||||
<div className="max-w-4xl mx-auto">
|
||||
<h1 className="text-2xl font-bold text-gray-900 mb-6">
|
||||
Configurações do Perfil
|
||||
</h1>
|
||||
|
||||
<Tabs defaultValue="personal">
|
||||
<TabsList>
|
||||
<TabsTrigger value="personal">Informações Pessoais</TabsTrigger>
|
||||
<TabsTrigger value="preferences">Preferências</TabsTrigger>
|
||||
<TabsTrigger value="accessibility">Acessibilidade</TabsTrigger>
|
||||
<TabsTrigger value="notifications">Notificações</TabsTrigger>
|
||||
</TabsList>
|
||||
|
||||
<TabsContent value="personal">
|
||||
<div className="space-y-6">
|
||||
<div className="flex items-center gap-4">
|
||||
<AvatarUpload />
|
||||
<div>
|
||||
<h3 className="font-medium">Foto do Perfil</h3>
|
||||
<p className="text-sm text-gray-500">
|
||||
JPG ou PNG, máximo 2MB
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="grid grid-cols-2 gap-4">
|
||||
<Input
|
||||
label="Nome Completo"
|
||||
name="fullName"
|
||||
placeholder="Seu nome completo"
|
||||
/>
|
||||
<Input
|
||||
label="Nome Social/Apelido"
|
||||
name="nickname"
|
||||
placeholder="Como prefere ser chamado"
|
||||
/>
|
||||
<DatePicker
|
||||
label="Data de Nascimento"
|
||||
name="birthDate"
|
||||
/>
|
||||
<Select
|
||||
label="Gênero"
|
||||
name="gender"
|
||||
options={[
|
||||
{ value: 'male', label: 'Masculino' },
|
||||
{ value: 'female', label: 'Feminino' },
|
||||
{ value: 'non_binary', label: 'Não-binário' },
|
||||
{ value: 'other', label: 'Outro' },
|
||||
{ value: 'prefer_not_to_say', label: 'Prefiro não dizer' }
|
||||
]}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</TabsContent>
|
||||
</Tabs>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@ -1,5 +1,5 @@
|
||||
export { StudentDashboardLayout } from './StudentDashboardLayout';
|
||||
export { StudentDashboardPage } from './StudentDashboardPage';
|
||||
export { StudentStoriesPage } from './StudentStoriesPage';
|
||||
export { CreateStoryPage } from './CreateStoryPage';
|
||||
export { StudentDashboard } from './StudentDashboard';
|
||||
export { StudentClassPage } from './StudentClassPage';
|
||||
export { StudentSettingsPage } from './StudentSettingsPage';
|
||||
export { StoryPage } from './StoryPage';
|
||||
71
src/pages/student/SettingsPage.tsx
Normal file
71
src/pages/student/SettingsPage.tsx
Normal file
@ -0,0 +1,71 @@
|
||||
import { AvatarUpload } from "@/components/ui/avatar-upload";
|
||||
import { DatePicker } from "@/components/ui/date-picker";
|
||||
import { Input } from "@/components/ui/input";
|
||||
import { Select } from "@/components/ui/select";
|
||||
import { Tabs, TabsContent, TabsList, TabsTrigger } from "@radix-ui/react-tabs";
|
||||
|
||||
export function StudentSettingsPage() {
|
||||
return (
|
||||
<div className="max-w-4xl mx-auto py-8 px-4">
|
||||
<h1 className="text-2xl font-bold text-gray-900 mb-6">
|
||||
Configurações do Perfil
|
||||
</h1>
|
||||
|
||||
{/* Seções em Tabs */}
|
||||
<Tabs defaultValue="personal">
|
||||
<TabsList>
|
||||
<TabsTrigger value="personal">Informações Pessoais</TabsTrigger>
|
||||
<TabsTrigger value="preferences">Preferências</TabsTrigger>
|
||||
<TabsTrigger value="accessibility">Acessibilidade</TabsTrigger>
|
||||
<TabsTrigger value="notifications">Notificações</TabsTrigger>
|
||||
</TabsList>
|
||||
|
||||
<TabsContent value="personal">
|
||||
<div className="space-y-6">
|
||||
{/* Avatar Upload */}
|
||||
<div className="flex items-center gap-4">
|
||||
<AvatarUpload />
|
||||
<div>
|
||||
<h3 className="font-medium">Foto do Perfil</h3>
|
||||
<p className="text-sm text-gray-500">
|
||||
JPG ou PNG, máximo 2MB
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Informações Básicas */}
|
||||
<div className="grid grid-cols-2 gap-4">
|
||||
<Input
|
||||
label="Nome Completo"
|
||||
name="fullName"
|
||||
placeholder="Seu nome completo"
|
||||
/>
|
||||
<Input
|
||||
label="Nome Social/Apelido"
|
||||
name="nickname"
|
||||
placeholder="Como prefere ser chamado"
|
||||
/>
|
||||
<DatePicker
|
||||
label="Data de Nascimento"
|
||||
name="birthDate"
|
||||
/>
|
||||
<Select
|
||||
label="Gênero"
|
||||
name="gender"
|
||||
options={[
|
||||
{ value: 'male', label: 'Masculino' },
|
||||
{ value: 'female', label: 'Feminino' },
|
||||
{ value: 'non_binary', label: 'Não-binário' },
|
||||
{ value: 'other', label: 'Outro' },
|
||||
{ value: 'prefer_not_to_say', label: 'Prefiro não dizer' }
|
||||
]}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</TabsContent>
|
||||
|
||||
{/* Outras tabs... */}
|
||||
</Tabs>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@ -16,6 +16,9 @@ import { StudentsPage } from '../pages/dashboard/students/StudentsPage';
|
||||
import { AddStudentPage } from '../pages/dashboard/students/AddStudentPage';
|
||||
import { DemoPage } from '../pages/demo/DemoPage';
|
||||
import { StoryPage } from '../pages/story/StoryPage';
|
||||
import { StudentSettingsPage } from '../pages/student-dashboard/StudentSettingsPage';
|
||||
import { StudentDashboardLayout } from '../pages/student-dashboard/StudentDashboardLayout';
|
||||
import { StudentDashboard, StudentClassPage } from '../pages/student-dashboard';
|
||||
import React from 'react';
|
||||
|
||||
export const router = createBrowserRouter([
|
||||
@ -125,4 +128,22 @@ export const router = createBrowserRouter([
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
path: '/aluno',
|
||||
element: <StudentDashboardLayout />,
|
||||
children: [
|
||||
{
|
||||
index: true,
|
||||
element: <StudentDashboard />,
|
||||
},
|
||||
{
|
||||
path: 'configuracoes',
|
||||
element: <StudentSettingsPage />
|
||||
},
|
||||
{
|
||||
path: 'turmas/:classId',
|
||||
element: <StudentClassPage />
|
||||
}
|
||||
]
|
||||
}
|
||||
]);
|
||||
@ -13,7 +13,10 @@
|
||||
"strict": true,
|
||||
"noUnusedLocals": false,
|
||||
"noUnusedParameters": false,
|
||||
"noFallthroughCasesInSwitch": true
|
||||
"noFallthroughCasesInSwitch": true,
|
||||
"paths": {
|
||||
"@/*": ["./src/*"]
|
||||
}
|
||||
},
|
||||
"include": ["src"]
|
||||
}
|
||||
|
||||
Loading…
Reference in New Issue
Block a user