Compare commits

..

73 Commits

Author SHA1 Message Date
Mohamed Maoulainine Maoulainine
8b863ee4b1 fix: adminview
Some checks failed
Format / formatting (push) Successful in 5s
Build / build (push) Successful in 38s
CI / build (push) Failing after 8s
Format / formatting (pull_request) Successful in 7s
2025-04-16 11:59:03 +02:00
6b3cb2610d feat: adding signup page
Some checks failed
Format / formatting (push) Successful in 5s
Build / build (push) Successful in 38s
CI / build (push) Failing after 8s
Format / formatting (pull_request) Successful in 5s
2025-04-16 10:59:23 +02:00
ba99b3c2b0 Merge branch 'front_foundation' of https://gitea.piair.dev/piair/MyINPulse into front_foundation
Some checks failed
Format / formatting (push) Successful in 6s
Build / build (push) Successful in 37s
CI / build (push) Failing after 8s
Format / formatting (pull_request) Successful in 5s
2025-04-16 10:36:59 +02:00
31d82f6271 fix:... 2025-04-16 10:36:54 +02:00
37e631a096 fix: fixing conflits 2025-04-16 10:36:06 +02:00
6306a00eca feat: contact button opens the partage page 2025-04-16 10:33:33 +02:00
Mohamed Maoulainine Maoulainine
c4ba7646d5 fix: typo
Some checks failed
Format / formatting (push) Successful in 8s
Build / build (push) Successful in 47s
CI / build (push) Failing after 12s
Format / formatting (pull_request) Successful in 6s
2025-04-15 23:13:02 +02:00
Mohamed Maoulainine Maoulainine
4e1908d528 Merge branch 'front_foundation' of https://gitea.piair.dev/piair/MyINPulse into front_foundation
Some checks failed
Format / formatting (push) Successful in 6s
Build / build (push) Successful in 38s
CI / build (push) Failing after 11s
Format / formatting (pull_request) Successful in 5s
2025-04-14 01:33:06 +02:00
Mohamed Maoulainine Maoulainine
03bbc77e8a fix: linting all the files possible error may still occur but i just ignored them cuz it's commented code, probably will fix them later if it persists 2025-04-14 01:32:29 +02:00
ad1fd45bed Merge branch 'front_foundation' of https://gitea.piair.dev/piair/MyINPulse into front_foundation
Some checks failed
Format / formatting (push) Successful in 5s
Build / build (push) Successful in 38s
Format / formatting (pull_request) Successful in 5s
CI / build (push) Failing after 8s
2025-04-12 22:23:20 +02:00
f0c4a3a10d adding formulaire pour le signup d'un entrepreneur (not yet working) 2025-04-12 22:23:17 +02:00
Mohamed Maoulainine Maoulainine
70658e4fb9 fix: fixed an issue with the server by adding the dependency of jwt
Some checks failed
CI / build (push) Waiting to run
Format / formatting (push) Successful in 6s
Build / build (push) Has been cancelled
Format / formatting (pull_request) Successful in 6s
2025-04-12 22:22:22 +02:00
Mohamed Maoulainine Maoulainine
2b31753265 feat: added an login page but the auth issue is still persisting, found a way to interprete the token the line is commneted in case it breaks code or needs a change
Some checks failed
Format / formatting (push) Successful in 6s
Build / build (push) Successful in 39s
CI / build (push) Failing after 8s
Format / formatting (pull_request) Successful in 5s
2025-04-12 03:55:30 +02:00
f8991e90ab feat: using bootstrap..
Some checks failed
Format / formatting (push) Successful in 7s
Build / build (push) Successful in 43s
CI / build (push) Failing after 11s
Format / formatting (pull_request) Successful in 6s
2025-04-11 19:42:38 +02:00
Mohamed Maoulainine Maoulainine
60290956ec feat: added form to add project and pending project section for admin, i forgot to push them
Some checks failed
Format / formatting (push) Successful in 5s
Build / build (push) Successful in 38s
CI / build (push) Failing after 8s
Format / formatting (pull_request) Successful in 5s
2025-04-09 00:46:44 +02:00
Mohamed Maoulainine Maoulainine
b9647ce36e Merge branch 'front_foundation' of https://gitea.piair.dev/piair/MyINPulse into front_foundation
Some checks failed
Format / formatting (push) Successful in 5s
Build / build (push) Successful in 38s
CI / build (push) Failing after 8s
Format / formatting (pull_request) Successful in 5s
2025-04-09 00:10:04 +02:00
8c4b9ceb9d feat: still enhancing the visuals
Some checks failed
Format / formatting (push) Successful in 5s
Build / build (push) Successful in 38s
CI / build (push) Has been cancelled
Format / formatting (pull_request) Successful in 5s
2025-04-09 00:19:56 +02:00
Mohamed Maoulainine Maoulainine
84d8d4523b feat: added a function for post and delete that follows callApi that only does get 2025-04-09 00:09:58 +02:00
647812576e feat: enhancing visuals
Some checks failed
Format / formatting (push) Successful in 5s
Build / build (push) Successful in 38s
CI / build (push) Failing after 8s
Format / formatting (pull_request) Successful in 5s
2025-04-09 00:05:15 +02:00
Mohamed Maoulainine Maoulainine
2dfee66958 fix: app
Some checks failed
Format / formatting (push) Successful in 7s
Build / build (push) Successful in 39s
CI / build (push) Failing after 8s
Format / formatting (pull_request) Successful in 5s
2025-04-08 23:06:02 +02:00
Mohamed Maoulainine Maoulainine
2b1666c949 ongoing fix: working through the auth issues, we got the test working again but the requests now give an error code 403
Some checks failed
Format / formatting (push) Successful in 6s
Build / build (push) Successful in 39s
CI / build (push) Failing after 8s
Format / formatting (pull_request) Successful in 8s
2025-04-06 02:02:28 +02:00
Mohamed Maoulainine Maoulainine
0c724cae7f Merge branch 'front_foundation' of https://gitea.piair.dev/piair/MyINPulse into front_foundation
Some checks failed
Format / formatting (push) Successful in 8s
Build / build (push) Successful in 43s
CI / build (push) Failing after 8s
Format / formatting (pull_request) Successful in 5s
2025-04-05 21:10:23 +02:00
Mohamed Maoulainine Maoulainine
6de45801d2 unfix: pushing a version that doesn't work for review cuz i don't really get what' wrong 2025-04-05 21:10:12 +02:00
03897e1139 feat: mode admin added 'first try'
Some checks failed
Format / formatting (push) Successful in 6s
Build / build (push) Successful in 37s
CI / build (push) Failing after 7s
Format / formatting (pull_request) Successful in 5s
2025-04-02 11:51:39 +02:00
00a733c03b fix: manine try now
Some checks failed
Format / formatting (push) Successful in 6s
Build / build (push) Successful in 38s
CI / build (push) Failing after 8s
Format / formatting (pull_request) Successful in 5s
2025-04-02 10:52:41 +02:00
3dc8131c33 merging
Some checks failed
Format / formatting (push) Successful in 7s
Build / build (push) Successful in 39s
CI / build (push) Failing after 8s
Format / formatting (pull_request) Successful in 6s
2025-04-02 08:44:23 +02:00
Mohamed Maoulainine Maoulainine
7c271d8c47 Merge branch 'main' into front_foundation
Some checks failed
Format / formatting (push) Successful in 6s
Build / build (push) Successful in 39s
CI / build (push) Failing after 8s
Format / formatting (pull_request) Successful in 5s
2025-04-01 01:38:19 +02:00
259d56271c Merge branch 'openapi_integration' into front_foundation 2025-03-26 12:20:25 +01:00
Mohamed Maoulainine Maoulainine
b9f3bbbe15 fix: Adnane?
Some checks failed
CI / build (push) Failing after 8s
2025-03-26 11:59:16 +01:00
14a2a59786 merging
Some checks failed
CI / build (push) Failing after 7s
2025-03-26 11:57:13 +01:00
0ae6e7dfda merging 2025-03-26 11:53:49 +01:00
15ccb5630a MERGING 2025-03-26 11:52:04 +01:00
4ec292cca7 fix: merging 2025-03-26 11:50:23 +01:00
14a953536a merging 2025-03-26 11:45:40 +01:00
288f983816 merging 2025-03-26 11:44:49 +01:00
dd6032f3ef fix: css.. 2025-03-26 11:38:33 +01:00
Mohamed Maoulainine Maoulainine
7e2f5bc506 Merge branch 'front_foundation' of https://gitea.piair.dev/piair/MyINPulse into front_foundation
Some checks are pending
CI / build (push) Waiting to run
2025-03-26 11:32:14 +01:00
Mohamed Maoulainine Maoulainine
4ef92efd0e fix: js to ts in header 2025-03-26 11:31:54 +01:00
e769dd6757 fix: css fixing (again) 2025-03-26 11:30:59 +01:00
Mohamed Maoulainine Maoulainine
550a51523f Merge branch 'front_foundation' of https://gitea.piair.dev/piair/MyINPulse into front_foundation 2025-03-26 11:24:28 +01:00
323cb05388 fix: css fixing 2025-03-26 11:07:50 +01:00
Mohamed Maoulainine Maoulainine
7e0851bfef Merge branch 'front_foundation' of https://gitea.piair.dev/piair/MyINPulse into front_foundation 2025-03-24 12:43:04 +01:00
Mohamed Maoulainine Maoulainine
98b6d167e8 fix: minor bugs on views 2025-03-24 12:34:17 +01:00
79baddb8f6 fix + feat: fixed some bugs + added a mock parser since the damn backend is not working, not finished tho (bugs) 2025-03-23 21:59:27 +01:00
Mohamed Maoulainine Maoulainine
0f8c83c2e2 feat: adding the endpoints' doc to the branch for an easy access 2025-03-21 01:20:34 +01:00
Mohamed Maoulainine Maoulainine
fad52644d2 fix : merged the main with the front branch and fixed all possible issues 2025-03-21 01:16:49 +01:00
Mohamed Maoulainine Maoulainine
5f51a1008b fix: fixing conflicts 2025-03-20 22:42:49 +01:00
279c171ba2 fix: Doc added (for me) 2025-03-19 11:54:34 +01:00
9ba8e3e84e feat: fake-data can be edited, switching to end-points branch 2025-03-19 10:52:12 +01:00
6de38a9725 feat: edit-mode added, to test it use the fake api in the 'fake_data' folder 2025-03-19 09:48:04 +01:00
067eeb9494 docs: added examples and changed some weird character
All checks were successful
CI / build (push) Successful in 11s
2025-02-26 11:31:45 +01:00
Mohamed Maoulainine Maoulainine
f48b570494 fix: to composition api 2025-02-26 03:39:59 +01:00
Mohamed Maoulainine Maoulainine
0733f8d5af Merge branch 'front_foundation' of https://gitea.piair.dev/piair/MyINPulse into front_foundation 2025-02-25 22:53:26 +01:00
Mohamed Maoulainine Maoulainine
8071c01c5d fix: fixing the issues regarding the use of href 2025-02-25 22:53:15 +01:00
b355463dd9 docs: added needed modications and reorganized yaml file
All checks were successful
CI / build (push) Successful in 11s
2025-02-25 22:34:33 +01:00
4ee3d9bc44 fix: data from api is now correctly displayed 2025-02-19 12:15:39 +01:00
d75d45e204 fix + feat: api test + error fix 2025-02-19 11:02:15 +01:00
79e949bdd4 feat: configured gradle file to generate code for api interface from swagger specification
All checks were successful
CI / build (push) Successful in 11s
2025-02-19 01:36:52 +01:00
Mohamed Maoulainine Maoulainine
9f3754776f fix: fixed the code for AdminMain 2025-02-18 22:17:57 +01:00
651fb2b1a1 feat: adding first fake api (use this cmd in fake_data reportory: json-server --watch db.json --port 5000) + fetching witch axios for now (npm install axios) 2025-02-18 07:20:58 +01:00
Mohamed Maoulainine Maoulainine
aa5988ce75 fix: emptyed the app.vue and did some code reorganisation 2025-02-17 23:48:28 +01:00
Mohamed Maoulainine Maoulainine
9ae18e1e4b feat : adding an agenda, not included to the main page yet 2025-02-17 21:51:27 +01:00
ef8c8e896d feat: added openapi documentation to the project
All checks were successful
CI / build (push) Successful in 11s
2025-02-12 20:59:13 +01:00
22ebb0e1f4 feat: enhancing canvass layout 2025-02-11 20:47:37 +01:00
09e4b3262f feat: switching to composition API standard 2025-02-11 19:56:52 +01:00
6a3d4239ab feat: canvas are now generic 2025-02-10 22:46:59 +01:00
9d71c93b5b feat: layout changes 2025-02-10 15:53:10 +01:00
Mohamed Maoulainine Maoulainine
5145b833ae feat: rendez-vous agenda for admin and user 2025-02-10 15:15:58 +01:00
Mohamed Maoulainine Maoulainine
4080cee818 Meeeerge...
Merge branch 'main' of https://gitea.piair.dev/piair/MyINPulse
2025-02-08 22:40:42 +01:00
Mohamed Maoulainine Maoulainine
f4d73654d1 some minor changes on the main page 2025-02-08 22:40:35 +01:00
4fda5513a9 error corrected 2025-02-08 20:33:03 +01:00
32407b0e8f conflits... 2025-02-08 20:23:43 +01:00
b30e1196f4 canvas included in the main page, still shiting with vue 2025-02-08 20:18:44 +01:00
27 changed files with 2884 additions and 71 deletions

View File

@ -0,0 +1,741 @@
openapi: 3.0.3
info:
title: MyInpulse Backend Api
description: this document servers as a documentation for the backend api.
version: 0.0.0
tags:
- name: Entrepreneurs API
description: La partie de l'api dédiée aux entrepreneurs
- name: Admin API
description: La partie de l'api dédiée aux entrepreneurs
- name: Shared API
description: La partie de l'api dédiée aux entrepreneurs et admins
components:
schemas:
user:
type: object
properties:
nom:
type: string
prenom:
type: string
email:
type: string
example: "example@exmaple.com"
secondaryEmail:
type: string
example: "example@exmaple.com"
tel:
type: string
example: "0612345678"
user-entrepreneur:
type: object
properties:
user:
$ref: "#/components/schemas/user"
entrepreneur:
type: object
properties:
ecole:
type: string
example: "enseirb"
filiere:
type: string
example: "info"
status:
type: boolean
example: false
user-admin:
type: object
properties:
admin:
$ref: "#/components/schemas/user"
securitySchemes:
MyINPulse:
type: oauth2
flows:
implicit:
authorizationUrl: https://petstore3.swagger.io/oauth/authorize
scopes:
MyINPulse-admin: Administrateur
MyINPulse-entrepreneur: Utilisateur
paths:
# _ ____ __ __ ___ _ _ _ ____ ___
# / \ | _ \| \/ |_ _| \ | | / \ | _ \_ _|
# / _ \ | | | | |\/| || || \| | / _ \ | |_) | |
# / ___ \| |_| | | | || || |\ | / ___ \| __/| |
# /_/ \_\____/|_| |_|___|_| \_| /_/ \_\_| |___|
#
/admin/projects:
get:
summary: Retourne la liste of projets associés à l'admin
tags:
- Admin API
security:
- MyINPulse:
- MyINPulse-admin
description:
JSON array of who's elements are objects containing necessary information for the view
(project name, entrepreneur names, etc..)
of the projects an admin is watching over.
responses:
"200":
description: OK
content:
application/json:
schema:
type: array
items:
type: object
properties:
name:
type: string
E_names:
type: string
description: entrepreneur names
"400":
description: Bad request
"401":
description: Authorization information is missing or invalid
/admin/projects/pending/decision:
post:
summary: valider un projet en attente de validation
tags:
- Admin API
description:
if the request is accepted the status of the
project is changed to ongoing, entrepreneur
account is confirmed and the project is linked
to the admin accepting the request and the
entrepreneur requesting it. Else the pending
project and user info are deleted.
security:
- MyINPulse:
- MyINPulse-admin
requestBody:
required: true
content:
application/json:
schema:
type: object
properties:
pedingProjectId:
type: integer
decision:
type: boolean
responses:
"200":
description: OK
"400":
description: Bad request
"401":
description: Authorization information is
missing or invalid
/admin/projects/add:
post:
summary: Ajout manuel d'un projet
description:
Adds a project with the
inputed details
tags:
- Admin API
security:
- MyINPulse:
- MyINPulse-admin
requestBody:
required: true
content:
application/json:
schema:
type: object
properties:
name:
type: string
founder:
$ref: "#/components/schemas/user-entrepreneur"
responses:
"200":
description: OK
"400":
description: Bad request
"401":
description: Authorization information is
missing or invalid
/admin/appointments/report/{appointmentId}:
put:
summary: enregistrer un rapport du rendez-vous
description:
Generate a PDF file formatted
from input text and links it
to the appointement.
tags:
- Admin API
security:
- MyINPulse:
- MyINPulse-admin
parameters:
- in: path
name: appointmentId
required: true
schema:
type: integer
requestBody:
required: true
content:
application/json:
schema:
type: object
properties:
title:
type: string
body:
type: string
conclusion:
type: string
responses:
"200":
description: OK
"400":
description: Bad request
"401":
description: Authorization information is
missing or invalid
post:
summary: modifier un rapport déja éxistant du rendez-vous
description:
Modifies the report file to input
text and links it to the appointement.
tags:
- Admin API
security:
- MyINPulse:
- MyINPulse-admin
parameters:
- in: path
name: appointmentId
required: true
schema:
type: integer
requestBody:
required: true
content:
application/json:
schema:
type: object
properties:
title:
type: string
body:
type: string
conclusion:
type: string
responses:
"200":
description: OK
"400":
description: Bad request
"401":
description: Authorization information is
missing or invalid
/admin/projects/remove/{projectId}:
delete:
summary: supression d'un project
description:
Removes the project
with the inputed id projectId
tags:
- Admin API
security:
- MyINPulse:
- MyINPulse-admin
parameters:
- in: path
name: projectId
required: true
schema:
type: integer
responses:
"200":
description: OK
"400":
description: Bad request
"401":
description: Authorization information is
missing or invalid
/admin/projects/pending:
get:
summary: Retourne la liste des projets en attente de validation
tags:
- Admin API
security:
- MyINPulse:
- MyINPulse-admin
description:
JSON array of who's elements are objects containing
necessary information for the view (project name,
entrepreneur names, etc..) of all pending projects.
responses:
"200":
description: OK
content:
application/json:
schema:
type: array
items:
type: object
properties:
name:
type: string
founder:
$ref: "#/components/schemas/user-entrepreneur"
"400":
description: Bad request
"401":
description: Authorization information is missing or invalid
#
# ____ _ _ _ ____ ___
# / ___|| |__ __ _ _ __ ___ __| | / \ | _ \_ _|
# \___ \| '_ \ / _` | '__/ _ \/ _` | / _ \ | |_) | |
# ___) | | | | (_| | | | __/ (_| | / ___ \| __/| |
# |____/|_| |_|\__,_|_| \___|\__,_| /_/ \_\_| |___|
#
/shared/appointments/upcoming:
get:
summary: Retourne la list des prochains rendez-vous de l'utilisateur
tags:
- Shared API
security:
- MyINPulse:
- MyINPulse-admin
- MyINPulse-entrepreneur
description:
JSON array of upcoming appointment data (name, date, time etc..) for a user.
responses:
"200":
description: OK
content:
application/json:
schema:
type: array
items:
type: object
properties:
name:
type: string
date:
type: string
time:
type: string
"400":
description: Bad request
"401":
description: Authorization information is missing or invalid
/shared/projects/lcsection/{projectId}/{title}/{date}:
get:
summary: Retourne la liste de sections de LC avec un titre donné
tags:
- Shared API
security:
- MyINPulse:
- MyINPulse-admin
- MyINPulse-entrepreneur
description:
JSON array containing Lean Canvas
section data with a title for the
current date (or given date if the
date parameter is passed)
parameters:
- in: path
required: true
name: projectId
schema:
type: integer
- in: path
required: true
description: this number can be 1, 2,...,8. It is associated with the title of the lcsection
name: title
schema:
type: integer
enum: [1, 2, 3, 4, 5, 6, 7, 8]
- in: path
required: true
name: date
description: the date corresponding to the wanted version of lc section. "Nan" for the latest version
example: "NaN"
schema:
type: string
responses:
"200":
description: OK
content:
application/json:
schema:
type: array
items:
type: object
properties:
section:
type: string
txt:
type: string
time:
type: string
"400":
description: Bad request
"401":
description: Authorization information is missing or invalid
/shared/projects/entrepreneurs/{projectId}:
get:
summary: Retourne la liste d'entrepreneurs associée à un projet donné
tags:
- Shared API
security:
- MyINPulse:
- MyINPulse-admin
- MyINPulse-entrepreneur
description:
JSON array of entrepreneur
names associated with a project
parameters:
- in: path
name: projectId
schema:
type: integer
required: true
responses:
"200":
description: OK
content:
application/json:
schema:
type: array
items:
$ref: "#/components/schemas/user-entrepreneur"
"400":
description: Bad request
"401":
description: Authorization information is missing or invalid
/shared/projects/admin/{projectId}:
get:
summary: Retourne les informations de l'admin qui accompagne le projet
tags:
- Shared API
security:
- MyINPulse:
- MyINPulse-admin
- MyINPulse-entrepreneur
description:
JSON object containing information (name, gmail, tel, etc..)
the admin supervising the project with id projectID.
parameters:
- in: path
name: projectId
schema:
type: integer
required: true
responses:
"200":
description: OK
content:
application/json:
schema:
$ref: "#/components/schemas/user-admin"
"400":
description: Bad request
"401":
description: Authorization information is
missing or invalid
/shared/projects/appointments/{projectId}:
get:
summary: Retourne les rendez-vous du projet
tags:
- Shared API
security:
- MyINPulse:
- MyINPulse-admin
- MyINPulse-entrepreneur
description:
JSON array of upcoming and past appointment
data for the project with id projectID.
parameters:
- in: path
name: projectId
schema:
type: integer
required: true
responses:
"200":
description: OK
content:
application/json:
schema:
type: array
items:
type: object
properties:
appointementId:
type: integer
name:
type: string
date:
type: string
time:
type: string
"400":
description: Bad request
"401":
description: Authorization information is
missing or invalid
/shared/projects/appointments/report/{apointementId}:
get:
summary: Retourne le rapport pdf du rendez-vous
tags:
- Shared API
security:
- MyINPulse:
- MyINPulse-admin
- MyINPulse-entrepreneur
description:
PDF file containing the ap-
pointment report
parameters:
- in: path
name: apointementId
schema:
type: integer
required: true
responses:
"200":
description: OK
content:
application/pdf:
schema:
type: string
format: binary
"400":
description: Bad request
"401":
description: Authorization information is
missing or invalid
/shared/appointments/request:
post:
summary: demander un rendez-vous
description:
will add an appointement request request by the applicant
to have an appointment to be confirmed or denied by the
specified participants of the appointement.
tags:
- Shared API
security:
- MyINPulse:
- MyINPulse-entrepreneur
- MyINPulse-admin
requestBody:
description: \"participants\" property is an array containing userids of the participants in the appointement
required: true
content:
application/json:
schema:
type: object
properties:
title:
type: string
start_time:
type: string
end_time:
type: string
place:
type: string
applicantId:
type: integer
participants:
#/* */
type: array
items:
type: integer
responses:
"200":
description: OK
"400":
description: Bad request
"401":
description: Authorization information is
missing or invalid
# _____ _ _ _____ ____ _____ ____ ____ _____ _ _ _____ _ _ ____
# | ____| \ | |_ _| _ \| ____| _ \| _ \| ____| \ | | ____| | | | _ \
# | _| | \| | | | | |_) | _| | |_) | |_) | _| | \| | _| | | | | |_) |
# | |___| |\ | | | | _ <| |___| __/| _ <| |___| |\ | |___| |_| | _ <
# |_____|_|_\_| |_| |_| \_\_____|_| |_| \_\_____|_| \_|_____|\___/|_| \_\
# / \ | _ \_ _|
# / _ \ | |_) | |
# / ___ \| __/| |
# /_/ \_\_| |___|
#
/entrepreneur/projects/request:
post:
summary: demander la création et validation d'un projet
tags:
- Entrepreneurs API
description:
Adds project to pending projects
to then be accepted or rejected by
an admin
security:
- MyINPulse:
- MyINPulse-entrepreneur
requestBody:
required: true
content:
application/json:
schema:
type: object
properties:
name:
type: string
founder:
$ref: "#/components/schemas/user-entrepreneur"
responses:
"200":
description: OK
"400":
description: Bad request
"401":
description: Authorization information is
missing or invalid
/entrepreneur/lcsection/add/{projectId}:
post:
summary: ajouter une sections au LC
description:
Adds input data to the user's LC
with a specified title.
tags:
- Entrepreneurs API
security:
- MyINPulse:
- MyINPulse-entrepreneur
parameters:
- in: path
name: projectId
schema:
type: integer
required: true
requestBody:
required: true
content:
application/json:
schema:
type: object
properties:
title:
type: string
txt:
type: string
responses:
"200":
description: OK
"400":
description: Bad request
"401":
description: Authorization information is
missing or invalid
/entrepreneur/lcsection/modify/{sectionId}:
put:
summary: modifier les données d'une section LC
description:
Modifies input Lean Canvas section by changing it to
the information in the request body and changes the
time stamp.
tags:
- Entrepreneurs API
security:
- MyINPulse:
- MyINPulse-entrepreneur
parameters:
- in: path
name: sectionId
schema:
type: integer
required: true
requestBody:
required: true
content:
application/json:
schema:
type: object
properties:
title:
type: string
txt:
type: string
responses:
"200":
description: OK
"400":
description: Bad request
"401":
description: Authorization information is
missing or invalid
/entrepreneur/lcsection/remove/{sectionId}:
delete:
summary: supprimer une section LC.
description:
Deletes section from Lean Canvas
tags:
- Entrepreneurs API
security:
- MyINPulse:
- MyINPulse-entrepreneur
parameters:
- in: path
name: sectionId
schema:
type: integer
required: true
responses:
"200":
description: OK
"400":
description: Bad request
"401":
description: Authorization information is
missing or invalid

View File

@ -33,6 +33,8 @@ dev-front: clean vite keycloak
@cp config/frontdev.docker-compose.yaml docker-compose.yaml
@docker compose up -d --build
@cd ./front/MyINPulse-front/ && npm run dev
@echo "cd MyINPulse-back" && echo 'export $$(cat .env | xargs)'
@echo "./gradlew bootRun --args='--server.port=8081'"
prod: clean keycloak
@cp config/prod.env front/MyINPulse-front/.env
@ -43,6 +45,7 @@ prod: clean keycloak
dev-back: keycloak
@cp config/backdev.env front/MyINPulse-front/.env
@cp config/backdev.env .env

0
front/Dockerfile Normal file → Executable file
View File

View File

@ -0,0 +1,63 @@
{
"entrepreneurs": [
{ "id": 1, "name": "Alice", "email": "alice@example.com" },
{ "id": 2, "name": "Bob", "email": "bob@example.com" },
{ "id": 3, "name": "Charlie", "email": "charlie@example.com" }
],
"data": [
{
"projectId": 1,
"title": 1,
"title_text": "1. Problème",
"description": "3 problèmes essentiels à résoudre pour le client"
},
{
"projectId": 1,
"title": 2,
"title_text": "2. Segments",
"description": "Les segments de clientèle visés"
},
{
"projectId": 1,
"title": 3,
"title_text": "3. Valeur",
"description": "La proposition de valeur"
},
{
"projectId": 1,
"title": 4,
"title_text": "4. Solution",
"description": "Les solutions proposées"
},
{
"projectId": 1,
"title": 5,
"title_text": "5. Avantage",
"description": "Les avantages concurrentiels"
},
{
"projectId": 1,
"title": 6,
"title_text": "6. Canaux",
"description": "Les canaux de distribution"
},
{
"projectId": 1,
"title": 7,
"title_text": "7. Indicateurs",
"description": "Les indicateurs clés de performance"
},
{
"projectId": 1,
"title": 8,
"title_text": "8. Coûts",
"description": "Les coûts associés"
},
{
"projectId": 1,
"title": 9,
"title_text": "9. Revenus",
"description": "Les sources de revenus"
}
]
}

View File

@ -0,0 +1,2 @@
#!/usr/bin/bash
json-server --watch db.json --port 5000

View File

@ -10,6 +10,7 @@
"dependencies": {
"axios": "^1.7.9",
"cors": "^2.8.5",
"jwt-decode": "^4.0.0",
"keycloak-js": "^26.1.0",
"pinia": "^2.3.1",
"pinia-plugin-persistedstate": "^4.2.0",
@ -3588,6 +3589,15 @@
"graceful-fs": "^4.1.6"
}
},
"node_modules/jwt-decode": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/jwt-decode/-/jwt-decode-4.0.0.tgz",
"integrity": "sha512-+KJGIyHgkGuIq3IEBNftfhW/LfWhXUIY6OmyVWjliu5KH1y0fw7VQ8YndE2O4qZdMSd9SqbnC8GOcZEy0Om7sA==",
"license": "MIT",
"engines": {
"node": ">=18"
}
},
"node_modules/keycloak-js": {
"version": "26.1.0",
"resolved": "https://registry.npmjs.org/keycloak-js/-/keycloak-js-26.1.0.tgz",

View File

@ -18,7 +18,8 @@
"pinia": "^2.3.1",
"pinia-plugin-persistedstate": "^4.2.0",
"vue": "^3.5.13",
"vue-router": "^4.5.0"
"vue-router": "^4.5.0",
"jwt-decode": "^4.0.0"
},
"devDependencies": {
"@playwright/test": "^1.49.1",

View File

@ -1,47 +1,16 @@
<script setup lang="ts">
import { RouterView } from "vue-router";
import { /*RouterLink,*/ RouterView } from 'vue-router'
import ErrorWrapper from "@/views/errorWrapper.vue";
import ProjectComponent from "@/components/ProjectComponent.vue";
</script>
<template>
<HeaderComponent />
<error-wrapper></error-wrapper>
<div id="main">
<ProjectComponent
v-for="(project, index) in projects"
:key="index"
:project-name="project.name"
/>
</div>
<Header />
<ErrorWrapper />
<!--<RouterLink to="/">Home</RouterLink> | -->
<!--<RouterLink to="/canvas">Canvas</RouterLink> -->
<RouterView />
</template>
<script lang="ts">
import HeaderComponent from "@/components/HeaderComponent.vue";
export default {
name: "App",
components: {
HeaderComponent,
},
data() {
return {
projects: [
{
name: "Projet Alpha",
//link: './project-alpha.html',
//members: ['Alice', 'Bob', 'Charlie'],
},
{
name: "Projet Beta",
//link: './project-beta.html',
//members: ['David', 'Eve', 'Frank'],
},
],
};
},
};
</script>
<style scoped></style>

View File

@ -0,0 +1,102 @@
<template>
<form class="add-project-form" @submit.prevent="submitProject">
<h2>Ajouter un projet</h2>
<div class="form-group">
<label for="projectName">Nom du projet</label>
<input
id="projectName"
v-model="project.projectName"
type="text"
required
/>
</div>
<div class="form-group">
<label for="creationDate">Date de création</label>
<input
id="creationDate"
v-model="project.creationDate"
type="text"
placeholder="JJ-MM-AAAA"
required
/>
</div>
<div class="form-group">
<label for="logo">Logo</label>
<input
id="logo"
v-model="project.logo"
type="text"
placeholder="(à discuter)"
/>
</div>
<button type="submit">Ajouter</button>
</form>
</template>
<script setup lang="ts">
import { ref } from "vue";
import { postApi } from "@/services/api.ts";
const project = ref({
idProject: 0,
projectName: "",
creationDate: "",
logo: "to be discussed not yet fixed",
});
function submitProject() {
postApi("/admin/projects/add", project.value);
}
</script>
<style scoped>
.add-project-form {
max-width: 500px;
margin: 0 auto;
padding: 20px;
background: #fff;
border-radius: 10px;
box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1);
}
h2 {
margin-bottom: 20px;
font-size: 24px;
color: #333;
}
.form-group {
margin-bottom: 15px;
display: flex;
flex-direction: column;
}
label {
font-weight: bold;
margin-bottom: 5px;
}
input {
padding: 8px;
border-radius: 5px;
border: 1px solid #ccc;
}
button {
background-color: #4caf50;
color: white;
padding: 10px 15px;
border: none;
border-radius: 5px;
cursor: pointer;
}
button:hover {
background-color: #45a049;
}
</style>

View File

@ -0,0 +1,75 @@
<template>
<div id="agenda">
<h3>Rendez-vous</h3>
<table>
<tbody>
<tr v-for=" (p, index) in projectRDV" :key="index" >
<td>{{ p.projectName }} </td> <td>{{ p.date }}</td> <td>{{ p.lieu }}</td>
</tr>
</tbody>
</table>
</div>
</template>
<script setup lang="ts">
import { defineProps } from "vue";
interface rendezVous{
projectName: string,
date: string,
lieu: string,
}
const props = defineProps<{
projectRDV: rendezVous[]
}>();
</script>
<style scoped>
#agenda {
padding: 20px;
}
/* Table Styling */
table {
width: 100%;
border-collapse: collapse;
font-family: Arial, sans-serif;
text-align: left;
margin-top: 20px;
border: 1px solid #ccc;
}
/* Header Row (if exists) */
th {
background-color: #f4f4f4;
padding: 12px;
font-weight: bold;
border: 1px solid #ccc;
}
/* Table Body Rows */
tbody tr {
border-bottom: 1px solid #ddd;
transition: background-color 0.2s ease; /* Smooth hover effect */
}
tbody tr:hover {
background-color: #f9f9f9; /* Highlight row on hover */
}
/* Cells Styling */
td {
padding: 10px;
border: 1px solid #eee;
font-size: 14px;
vertical-align: middle; /* Align text to middle */
}
/* First Column Styling */
td:first-child {
text-align: center;
width: 50px; /* Adjust width as needed */
}
</style>

View File

@ -0,0 +1,173 @@
<script lang="ts" setup>
import { onMounted, ref } from "vue";
import { useRouter } from "vue-router";
import {jwtDecode} from "jwt-decode"; // i hope this doesn't break the code later
import { store } from "../main.ts";
import { callApi } from "@/services/api.ts";
const router = useRouter();
type TokenPayload = {
realm_access?: {
roles?: string[];
};
};
const customRequest = ref('');
onMounted(() => {
if (store.authenticated && store.user.token) {
try {
const decoded = jwtDecode<TokenPayload>(store.user.token);
const roles = decoded.realm_access?.roles || [];
if (roles.includes("MyINPulse-admin")) {
router.push("/");
} else if (roles.includes("entrepreneur")) {
router.push("/entrepreneur");
}
} catch (err) {
console.error("Failed to decode token", err);
}
}
});
</script>
<template>
<error-wrapper></error-wrapper>
<div class="auth-container">
<div class="auth-card">
<h1>Bienvenue</h1>
<div class="status" :class="store.authenticated ? 'success' : 'error'">
<p>
{{ store.authenticated ? '✅ Authenticated' : '❌ Not Authenticated' }}
</p>
</div>
<div class="actions">
<button @click="store.login">Login</button>
<button @click="store.logout">Logout</button>
<button @click="store.signup">Signup-admin</button>
<button @click="store.signup">Signup-Entrepreneur</button>
<button @click="store.refreshUserToken">Refresh Token</button>
</div>
<div v-if="store.authenticated" class="token-section" >
<p><strong>Access Token:</strong></p>
<pre>{{ store.user.token }}</pre>
<p><strong>Refresh Token:</strong></p>
<pre>{{ store.user.refreshToken }}</pre>
</div>
<div class="api-calls">
<h2>Test API Calls</h2>
<button @click="callApi('random')">Call Entrepreneur API</button>
<button @click="callApi('random2')">Call Admin API</button>
<button @click="callApi('unauth/dev')">Call Unauth API</button>
<div class="custom-call">
<input v-model="customRequest" placeholder="Custom endpoint" />
<button @click="callApi(customRequest)">Call</button>
</div>
</div>
</div>
</div>
</template>
<style scoped>
.auth-container {
display: flex;
justify-content: center;
align-items: center;
padding: 3rem 1rem;
min-height: 100vh;
background-color: #eef1f5;
font-family: Arial, sans-serif;
}
.auth-card {
background: white;
padding: 2rem;
border-radius: 12px;
box-shadow: 0 5px 15px rgba(0, 0, 0, 0.1);
width: 100%;
max-width: 600px;
}
h1 {
text-align: center;
margin-bottom: 1rem;
color: #333;
}
.status {
text-align: center;
margin-bottom: 1.5rem;
font-weight: bold;
}
.success {
color: green;
}
.error {
color: red;
}
.actions {
display: flex;
flex-wrap: wrap;
gap: 1rem;
justify-content: center;
margin-bottom: 1.5rem;
}
.actions button {
padding: 0.6rem 1rem;
background-color: #4a90e2;
border: none;
color: white;
border-radius: 8px;
cursor: pointer;
transition: background-color 0.2s;
}
.actions button:hover {
background-color: #357abd;
}
.token-section pre {
background: #f6f8fa;
padding: 0.5rem;
overflow-x: auto;
border: 1px solid #ddd;
border-radius: 6px;
margin-bottom: 1rem;
font-size: 0.85rem;
}
.api-calls {
margin-top: 2rem;
}
.api-calls h2 {
margin-bottom: 1rem;
color: #444;
font-size: 1.1rem;
}
.api-calls button {
margin-right: 0.5rem;
margin-bottom: 0.5rem;
}
.custom-call {
margin-top: 1rem;
display: flex;
gap: 0.5rem;
}
.custom-call input {
flex: 1;
padding: 0.5rem;
border: 1px solid #ccc;
border-radius: 6px;
}
</style>

View File

@ -0,0 +1,149 @@
<template>
<div class="project">
<div class="project-header">
<div class="project-title">
<h2>{{ projectName }}</h2>
<p>Projet mis le: {{ creationDate }}</p>
</div>
<div class="project-button">
<button id="accept" @click="acceptProject">Accepter</button>
<button id="refus" @click="refuseProject">Refuser</button>
</div>
</div>
</div>
</template>
<script setup lang="ts">
import { defineProps } from "vue";
import { postApi } from "@/services/api";
import { addNewMessage, color } from "@/services/popupDisplayer";
const props = defineProps<{
projectName: string;
creationDate: string;
}>();
const URI = "/admin/projects/pending/decision";
const sendDecision = (decision: "true" | "false") => {
postApi(
URI,
{
projectName: props.projectName,
decision,
},
() => {
addNewMessage(
`Projet ${props.projectName} ${decision === "true" ? "accepté" : "refusé"}`,
color.Green
);
},
(err) => {
addNewMessage(`Erreur lors de la décision`, color.Red);
console.error(err);
}
);
};
const acceptProject = () => sendDecision("true");
const refuseProject = () => sendDecision("false");
</script>
<style scoped>
.project {
background: linear-gradient(to right, #f4f4f4, #ffffff);
border: 1px solid #ddd;
border-radius: 10px;
padding: 20px;
margin: 20px 0;
box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1);
font-family: Arial, sans-serif;
}
/* Header Styling */
.project-header {
display: flex;
justify-content: space-between;
align-items: flex-start;
}
.project-title {
display: flex;
flex-direction: column;
}
.project-title h2 {
font-size: 20px;
color: #333;
margin: 0;
}
.project-title p {
font-size: 14px;
color: #777;
margin-top: 4px;
}
/* Button Container */
.project-buttons {
display: flex;
gap: 10px;
}
#accept {
background-color: #45a049;
}
#refus {
background-color: red;
}
button {
padding: 10px 15px;
color: white;
border: none;
cursor: pointer;
border-radius: 5px;
}
button:hover {
transform: scale(1.05);
}
#accept:hover {
background-color: #3e8e41;
}
#refus:hover {
background-color: darkred;
}
/* Project Body (unchanged) */
.project-body {
margin-top: 15px;
}
.project-body p {
font-size: 16px;
color: #555;
margin-bottom: 10px;
}
.project-body ul {
list-style-type: disc;
margin: 0;
padding-left: 20px;
}
.project-body ul li {
font-size: 14px;
color: #666;
line-height: 1.6;
}
</style>

View File

@ -1,21 +1,130 @@
<template>
<div class="project">
<div class="project" @click="goToLink" >
<div class="project-header">
<h2>{{ projectName }}</h2>
<h2 >{{ projectName }}</h2>
<div class="project-buttons">
<button class="contact-btn">Contact</button>
</div>
</div>
<div class="project-body">
<ul>
<li v-for="(name, index) in listName" :key="index">{{ name }}</li>
</ul>
</div>
</div>
</template>
<script lang="ts">
import type { PropType } from "vue";
export default {
name: "ProjectComponent",
props: {
projectName: {
type: Object as PropType<string>,
required: true,
},
},
<script setup lang="ts">
import { defineProps } from "vue";
import { useRouter } from 'vue-router'
const props = defineProps<{
projectName: string;
listName: string[];
projectLink: string;
}>();
const router = useRouter();
const goToLink = () => {
if (props.projectLink) {
router.push(props.projectLink);
}
};
</script>
<style scoped>
.project {
background: linear-gradient(to right, #f4f4f4, #ffffff);
border: 1px solid #ddd;
border-radius: 10px;
padding: 20px;
margin: 20px 0;
box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1);
font-family: Arial, sans-serif;
}
/* Header Styling */
.project-header {
display: flex;
justify-content: space-between;
align-items: center;
}
.project-header h2 {
font-size: 20px;
color: #333;
margin: 0;
}
/* Button Container */
.project-buttons {
display: flex;
gap: 10px;
}
.info-btn {
background-color: #4CAF50;
color: #fff;
}
.info-btn:hover {
background-color: #45a049;
transform: scale(1.05);
}
.contact-btn {
background-color: #007BFF;
color: #fff;
}
.contact-btn:hover {
background-color: #0056b3;
transform: scale(1.05);
}
.project-body {
margin-top: 15px;
}
.project-body p {
font-size: 16px;
color: #555;
margin-bottom: 10px;
}
.project-body ul {
list-style-type: disc;
margin: 0;
padding-left: 20px;
}
.project-body ul li {
font-size: 14px;
color: #666;
line-height: 1.6;
}
button {
padding: 10px 15px;
background-color: #007bff;
color: white;
border: none;
cursor: pointer;
border-radius: 5px;
}
button:hover {
background-color: #0056b3;
}
</style>

View File

@ -0,0 +1,396 @@
<template>
<div :class="['cell', { expanded }]" @click="handleClick">
<h3 class="fs-5 fw-medium">{{ titleText }}</h3>
<div v-for="(desc, index) in currentDescriptions" :key="index" class="section-bloc">
<!-- ADMIN -------------------------------------------------------------------------------------------->
<template v-if="IS_ADMIN">
<div class="description">
<p class="m-0">{{ desc }}</p>
</div>
</template>
<!-- ENTREP ------------------------------------------------------------------------------------------->
<template v-if="!IS_ADMIN">
<!-- Mode affichage -->
<template v-if="!isEditing[index]">
<div class="description">
<p class="m-0">{{ desc }}</p>
</div>
<div class="button-container">
<button v-if="expanded" class="edit-button" @click.stop="startEditing(index)">Éditer</button>
</div>
</template>
<!-- Mode édition -->
<template v-else>
<textarea v-model="editedDescriptions[index]" class="edit-input"></textarea>
<div class="button-container">
<button class="save-button" @click.stop="saveEdit(index)">Enregistrer</button>
<button class="cancel-button" @click.stop="cancelEdit(index)">Annuler</button>
</div>
</template>
</template>
</div>
<!---------------------------------------------------------------------------------------------------->
<template v-if="expanded">
<div class="canvas-exit-hint">
Cliquez n'importe pour quitter le canvas
</div>
</template>
</div>
</template>
<script setup lang="ts">
import { ref, defineProps, onMounted } from "vue";
import axios from "axios";
import { axiosInstance } from "@/services/api.ts";
const IS_MOCK_MODE = true;
const props = defineProps<{
projectId: number;
title: number;
titleText: string;
description: string;
is_admin: number;
}>();
const IS_ADMIN = props.is_admin;
const expanded = ref(false);
const currentDescriptions = ref<string[]>([]);
currentDescriptions.value[0] = props.description;
const editedDescriptions = ref<string[]>([]);
const isEditing = ref<boolean[]>([]);
onMounted(() => {
fetchData(props.projectId, props.title, "NaN", IS_MOCK_MODE);
});
/* FOR LOCAL DATABASE
const fetchData = async () => {
try {
const response = await axios.get("http://localhost:5000/data"); // Met à jour l'URL
if (response.data.length > 0) {
currentDescription.value = response.data[0].canva_data;
editedDescription.value = response.data[0].canva_data;
} else {
console.warn("Aucune donnée reçue.");
}
} catch (error) {
console.error("Erreur lors de la récupération des données :", error);
}
};
*/
// Fonction fetchData avec possibilité d'utiliser le mock
/* FOR FETCHING WITH AXIOS DIRECTLY
const fetchData = async (projectId: number, title: number, date: string, useMock = false) => {
try {
const responseData = useMock
? await mockFetch(projectId, title, date)
: (await axios.get<{ txt: string }[]>(
`http://localhost:5000/shared/projects/lcsection/${projectId}/${title}/${date}`
)).data;
if (responseData.length > 0) {
currentDescriptions.value = responseData.map((item) => item.txt);
editedDescriptions.value = [...currentDescriptions.value];
isEditing.value = Array(responseData.length).fill(false);
} else {
console.warn("Aucune donnée reçue.");
}
} catch (error) {
console.error("Erreur lors de la récupération des données :", error);
}
};
*/
// Fonction fetchData avec possibilité d'utiliser le mock
const fetchData = async (projectId: number, title: number, date: string, useMock = false) => {
try {
const responseData = useMock
? await mockFetch(projectId, title, date)
: (await axiosInstance.get<{ txt: string }[]>(
`/shared/projects/lcsection/${projectId}/${title}/${date}`
)).data;
if (responseData.length > 0) {
currentDescriptions.value = responseData.map((item) => item.txt);
editedDescriptions.value = [...currentDescriptions.value];
isEditing.value = Array(responseData.length).fill(false);
} else {
console.warn("Aucune donnée reçue.");
}
} catch (error) {
console.error("Erreur lors de la récupération des données :", error);
}
};
// Fonction de simulation de l'API
const mockFetch = async (projectId: number, title: number, date: string) => {
console.log(`Mock fetch pour projectId: ${projectId}, title: ${title}, date: ${date}`);
return new Promise<{ txt: string }[]>((resolve) => {
setTimeout(() => {
resolve([
{txt: "Ceci est une description 1 pour tester le front."},
{txt: "Deuxième description."},
{txt: "Troisième description."}
]);
}, 500); // Simule un délai réseau de 500ms
});
};
// Utilisation du mock dans handleClick pour tester sans serveur
const handleClick = async () => {
if (!expanded.value) {
await fetchData(props.projectId, props.title, "NaN", IS_MOCK_MODE);
} else if (!isEditing.value.includes(true)) {
// Réinitialiser les descriptions si aucune édition n'est en cours
currentDescriptions.value = [props.description];
editedDescriptions.value = [props.description];
}
if (!isEditing.value.includes(true)) {
expanded.value = !expanded.value;
}
};
const startEditing = (index: number) => {
isEditing.value[index] = true;
};
/*
const saveEdit = async (index: number) => {
try {
const id = index + 1; // À adapter selon l'ID réel des données
await axios.put(`http://localhost:5000/data/${id}`, {
canva_data: editedDescriptions.value[index]
});
// Mettre à jour l'affichage local après la mise à jour réussie
currentDescriptions.value[index] = editedDescriptions.value[index];
isEditing.value[index] = false;
} catch (error) {
console.error("Erreur lors de la mise à jour des données :", error);
}
};
*/
const saveEdit = async (index: number) => {
if (IS_MOCK_MODE) {
await mockSaveEdit(index);
} else {
try {
const id = index + 1;
await axios.put(`http://localhost:5000/data/${id}`, {
canva_data: editedDescriptions.value[index]
});
// Mettre à jour l'affichage local après la mise à jour réussie
currentDescriptions.value[index] = editedDescriptions.value[index];
isEditing.value[index] = false;
} catch (error) {
console.error("Erreur lors de la mise à jour des données :", error);
}
}
};
// Fonction de mock pour l'enregistrement
const mockSaveEdit = async (index: number) => {
try {
const id = index + 1;
console.log(`Mock save pour l'ID ${id} avec la description : ${editedDescriptions.value[index]}`);
await new Promise((resolve) => setTimeout(resolve, 500)); // Simulation de délai réseau
// Mettre à jour l'affichage local après la mise à jour réussie
currentDescriptions.value[index] = editedDescriptions.value[index];
isEditing.value[index] = false;
} catch (error) {
console.error("Erreur lors de la mise à jour des données mockées :", error);
}
};
const cancelEdit = (index: number) => {
editedDescriptions.value[index] = currentDescriptions.value[index];
isEditing.value[index] = false;
};
</script>
<style scoped>
@import "@/components/canvas/style-project.css";
.cell {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
text-align: center;
transition: all 0.3s ease;
cursor: pointer;
box-shadow: 0 4px 5px rgba(0, 0, 0, 0.1);
}
.expanded-content {
justify-content: flex-start !important;
}
.cell:not(.expanded):hover {
transform: scale(1.05);
box-shadow: 0 8px 9px rgba(0, 0, 0, 0.2);
}
.cell h3 {
font-size: 15px;
font-weight: 500;
font-family: 'Arial', sans-serif;
}
.p {
font-size: 10px;
color: #666;
font-family: 'Arial', sans-serif;
}
.expanded {
padding-top: 10%;
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
background: white;
z-index: 10;
display: flex;
align-items: center;
justify-content: flex-start;
box-shadow: 0 0 10px rgba(0, 0, 0, 0.2);
}
.description {
display: flex;
align-items: center;
justify-content: center;
text-align: center;
width: 100%;
height: 100%;
font-size: 16px;
margin-top: 10px;
margin-left: 2%;
margin-right: 4%;
}
.description + .p {
align-items: center;
justify-content: center;
text-align: center;
}
.edit-input {
width: 100%;
height: 100%;
padding: 10px;
border: 1px solid #ccc;
border-radius: 5px;
margin-top: 10px;
box-sizing: border-box;
margin-left: 2%;
}
.button-container {
display: block;
margin-top: 20px;
justify-content: center;
align-items: center;
gap: 10px;
padding-right: 1%;
}
.section-bloc ,.editing-section-bloc {
width: 100%;
justify-content: center;
align-items: center;
display: flex;
margin-right: 10%;
margin: 10px;
}
.edit-button {
width: 100px;
height: 40px;
border: none;
border-radius: 5px;
cursor: pointer;
transition: background 0.3s ease;
font-size: 12px;
margin-right: 20px;
}
.save-button, .cancel-button {
width: 100px;
height: 40px;
border: none;
border-radius: 5px;
cursor: pointer;
transition: background 0.3s ease;
font-size: 12px;
margin-bottom: 5px;
}
.edit-button {
background-color: #007bff;
color: white;
}
.save-button {
background-color: #28a745;
color: white;
}
.cancel-button {
background-color: #dc3545;
color: white;
}
.edit-button:hover {
background-color: #0056b3;
}
.save-button:hover {
background-color: #218838;
}
.cancel-button:hover {
background-color: #c82333;
}
.canvas-exit-hint {
font-size: 0.75rem;
color: #666;
position: fixed;
bottom: 10px;
left: 0;
width: 100%;
text-align: center;
z-index: 1000;
}
</style>

View File

@ -0,0 +1,218 @@
<template>
<header class="header">
<img src="../icons/logo inpulse.png" alt="INPulse Logo" class="logo" />
<div class="header-actions">
<div class="dropdown-wrapper">
<button class="contact-button" @click="toggleDropdown">Contact</button>
<div class="contact-dropdown" :class="{ 'dropdown-visible': isDropdownOpen }">
<button @click="contactAll">Contacter tous</button>
<button
v-for="(email, index) in entrepreneurEmails"
:key="index"
@click="contactSingle(email)"
>
{{ email }}
</button>
</div>
</div>
<RouterLink to="/" class="return-button">Retour</RouterLink>
</div>
</header>
</template>
<script setup lang="ts">
import { ref, onMounted } from "vue";
import axios from "axios";
const IS_MOCK_MODE = true;
const props = defineProps<{
projectId: number;
}>();
type Entrepreneur = {
idUser: number;
userSurname: string;
userName: string;
primaryMail: string;
secondaryMail: string;
phoneNumber: string;
school: string;
course: string;
sneeStatus: boolean;
};
const isDropdownOpen = ref(false);
const entrepreneurEmails = ref<string[]>([]);
const toggleDropdown = () => {
isDropdownOpen.value = !isDropdownOpen.value;
console.log("Dropdown toggled:", isDropdownOpen.value);
};
const fetchEntrepreneurs = async (projectId: number, useMock = IS_MOCK_MODE) => {
try {
const responseData: Entrepreneur[] = useMock
? await mockFetchEntrepreneurs(projectId)
: (await axios.get(`http://localhost:5000/shared/projects/entrepreneurs/${projectId}`)).data;
if (responseData.length > 0) {
entrepreneurEmails.value = responseData.map((item: Entrepreneur) => item.primaryMail);
} else {
console.warn("Aucun entrepreneur trouvé.");
}
} catch (error) {
console.error("Erreur lors de la récupération des entrepreneurs :", error);
}
};
// Fonction de simulation de l'API
const mockFetchEntrepreneurs = async (projectId :number) => {
console.log(`Mock fetch pour projectId: ${projectId}`);
return new Promise((resolve) => {
setTimeout(() => {
resolve([
{
idUser: 1,
userSurname: "Doe",
userName: "John",
primaryMail: "john.doe@example.com",
secondaryMail: "johndoe@backup.com",
phoneNumber: "612345678",
school: "ENSEIRB",
course: "Info",
sneeStatus: false
},
{
idUser: 2,
userSurname: "Smith",
userName: "Jane",
primaryMail: "jane.smith@example.com",
secondaryMail: "janesmith@backup.com",
phoneNumber: "698765432",
school: "ENSEIRB",
course: "Info",
sneeStatus: true
}
]);
}, 500);
});
};
const contactAll = () => {
const allEmails = entrepreneurEmails.value.join(", ");
navigator.clipboard.writeText(allEmails)
.then(() => {
alert("Tous les emails copiés dans le presse-papiers !");
window.open("https://partage.bordeaux-inp.fr/", "_blank");
})
.catch(err => {
console.error("Erreur lors de la copie :", err);
});
};
const contactSingle = (email: string) => {
navigator.clipboard.writeText(email)
.then(() => {
alert(`Adresse copiée : ${email}`);
window.open("https://partage.bordeaux-inp.fr/", "_blank");
})
.catch(err => {
console.error("Erreur lors de la copie :", err);
});
};
const copyToClipboard = (email: string) => {
navigator.clipboard.writeText(email).then(() => {
alert(`Adresse copiée : ${email}`);
}).catch(err => {
console.error("Erreur lors de la copie :", err);
});
};
onMounted(() => fetchEntrepreneurs(props.projectId, IS_MOCK_MODE));
</script>
<style scoped>
@import "@/components/canvas/style-project.css";
.header {
display: flex;
justify-content: space-between;
align-items: center;
padding: 15px 30px;
background-color: #f9f9f9;
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
}
.logo {
height: 50px;
}
.header-actions {
display: flex;
align-items: center;
gap: 20px;
position: relative;
}
.contact-button,
.return-button {
background-color: #009CDE;
color: white;
border: none;
padding: 10px 15px;
cursor: pointer;
font-size: 14px;
border-radius: 5px;
text-decoration: none;
transition: background-color 0.2s ease;
font-family: Arial, sans-serif;
}
.return-button:hover,
.contact-button:hover {
background-color: #007bad;
}
.contact-dropdown {
position: absolute;
top: 100%;
left: 0;
background-color: #000;
color: white;
box-shadow: 0px 4px 8px rgba(255, 255, 255, 0.2);
border-radius: 8px;
padding: 10px;
margin-top: 5px;
z-index: 1000;
min-width: 200px;
display: none;
}
.contact-dropdown button {
display: block;
width: 100%;
padding: 5px;
text-align: left;
border: none;
background: none;
cursor: pointer;
color: white;
}
.contact-dropdown button:hover {
background-color: #009CDE;
}
.contact-dropdown.dropdown-visible {
display: block;
}
</style>

View File

@ -0,0 +1,82 @@
<template>
<div class="canvas container-fluid">
<CanvasItem
v-for="(item, index) in items"
:key="index"
:title="item.title"
:titleText="item.title_text"
:description="item.description"
:project-id="item.projectId"
:class="['canvas-item', item.class, 'card', 'shadow', 'p-3']"
:is_admin=is_admin
/>
</div>
</template>
<script setup lang="ts">
import { ref, onMounted } from "vue";
import CanvasItem from "@/components/canvas/CanvasItem.vue";
const props = defineProps<{
is_admin: number;
}>();
const items = ref([
{ projectId: 1, title: 1, title_text: "1. Problème", description: "3 problèmes essentiels à résoudre pour le client", class: "Probleme" },
{ projectId: 1, title: 2, title_text: "2. Segments", description: "Les segments de clientèle visés", class: "Segments" },
{ projectId: 1, title: 3, title_text: "3. Valeur", description: "La proposition de valeur", class: "Valeur" },
{ projectId: 1, title: 4, title_text: "4. Solution", description: "Les solutions proposées", class: "Solution" },
{ projectId: 1, title: 5, title_text: "5. Avantage", description: "Les avantages concurrentiels", class: "Avantage" },
{ projectId: 1, title: 6, title_text: "6. Canaux", description: "Les canaux de distribution", class: "Canaux" },
{ projectId: 1, title: 7, title_text: "7. Indicateurs", description: "Les indicateurs clés de performance", class: "Indicateurs" },
{ projectId: 1, title: 8, title_text: "8. Coûts", description: "Les coûts associés", class: "Couts" },
{ projectId: 1, title: 9, title_text: "9. Revenus", description: "Les sources de revenus", class: "Revenus" }
]);
onMounted(() => {
const bootstrapCss = document.createElement('link')
bootstrapCss.rel = 'stylesheet'
bootstrapCss.href = 'https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/css/bootstrap.min.css'
bootstrapCss.integrity = 'sha384-QWTKZyjpPEjISv5WaRU9OFeRpok6YctnYmDr5pNlyT2bRjXh0JMhjY6hW+Fpc+NC'
bootstrapCss.crossOrigin = 'anonymous'
document.head.appendChild(bootstrapCss)
const bootstrapJs = document.createElement('script')
bootstrapJs.src = 'https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/js/bootstrap.bundle.min.js'
bootstrapJs.integrity = 'sha384-mQ93S0EhrF4Z1nM+fTflmYf0DyzsY5j7F5H3WlClDD6H3WUJh6kxBkF3GDW8n1j6'
bootstrapJs.crossOrigin = 'anonymous'
document.body.appendChild(bootstrapJs)
})
</script>
<style scoped>
@import "@/components/canvas/style-project.css";
.canvas {
display: grid;
grid-template-columns: repeat(10, 1fr);
grid-template-rows: repeat(6, 1fr);
gap: 12px;
padding: 30px;
/*background-color: #f8f9fa;*/
position: relative;
height: 90vh;
overflow: auto;
}
.Probleme { grid-column: 1 / 3; grid-row: 1 / 5; }
.Segments { grid-column: 9 / 11; grid-row: 1 / 5; }
.Valeur { grid-column: 5 / 7; grid-row: 1 / 5; }
.Solution { grid-column: 3 / 5; grid-row: 1 / 3; }
.Avantage { grid-column: 7 / 9; grid-row: 1 / 3; }
.Canaux { grid-column: 7 / 9; grid-row: 3 / 5; }
.Indicateurs { grid-column: 3 / 5; grid-row: 3 / 5; }
.Couts { grid-column: 1 / 6; grid-row: 5 / 7; }
.Revenus { grid-column: 6 / 11; grid-row: 5 / 7; }
.canvas-item {
/*background-color: white;*/
border: 1px solid #dee2e6;
border-radius: 0.5rem;
}
</style>

View File

@ -0,0 +1,156 @@
body {
font-family: Arial, sans-serif;
margin: 0;
padding: 10px;
}
.row {
display: flex;
}
.cell {
flex: 1;
border: 1px solid #ddd;
padding: 10px;
text-align: center;
background-color: #f1f1f1;
}
.produit {
background-color: #f9e4e4;
}
.marche {
background-color: #e4f1f9;
}
.valeur {
background-color: #f9f4e4;
}
h3 {
margin: 0;
font-size: 18px;
color: #333;
}
p {
margin: 5px 0 0;
font-size: 14px;
}
body {
font-family: Arial, sans-serif;
margin: 0;
padding: 0;
background-color: #f9f9f9;
}
h1 img {
height: 80px;
margin: 40px;
text-align: center;
}
.row {
display: flex;
margin-bottom: 10px;
}
#ade {
max-width: 1200px;
margin: 20px auto;
padding: 20px;
text-align: center;
background-color: #e8f5e9;
border: 2px solid #4caf50;
border-radius: 10px;
}
#ade h3 {
color: #2e7d32;
}
#ade p {
margin: 10px 0;
font-size: 16px;
color: #333;
}
header {
display: flex;
justify-content: space-between;
align-items: center;
padding: 10px 20px;
background-color: #fff;
border-bottom: 2px solid #ddd;
}
header img {
height: 60px;
}
header .contact-menu {
position: relative;
}
.contact-button, .return {
padding: 10px 15px;
border: none;
border-radius: 4px;
background-color: #2196f3;
color: #fff;
cursor: pointer;
}
.contact-button:hover, .return:hover {
background-color: #1976d2;
}
/* Dropdown styling */
.contact-dropdown {
position: absolute;
right: 0;
top: 50px;
display: none;
flex-direction: column;
gap: 10px;
padding: 15px;
background-color: #fff;
border: 1px solid #ddd;
border-radius: 8px;
box-shadow: 0 4px 8px rgba(0, 0, 0, 0.1);
z-index: 10;
}
.contact-dropdown button {
padding: 8px 12px;
border: none;
border-radius: 4px;
background-color: #4caf50;
color: #fff;
cursor: pointer;
}
.contact-dropdown button:hover {
background-color: #388e3c;
}
.return {
background-color: #f44336;
}
.return:hover {
background-color: #d32f2f;
}
.header-buttons {
display: flex;
align-items: center;
gap: 15px;
}
a{
color: white;
}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.1 KiB

After

Width:  |  Height:  |  Size: 39 KiB

View File

@ -26,6 +26,52 @@ keycloakService.CallInit(() => {
console.error(e);
createApp(App).mount("#app");
}
});
})
// this shit made by me so i can run the canva vue app
//createApp(App).use(router).mount('#app');
// TODO: fix the comment
/*
function tokenInterceptor () {
axios.interceptors.request.use(config => {
const keycloak = useKeycloak()
if (keycloak.authenticated) {
// Note that this is a simple example.
// you should be careful not to leak tokens to third parties.
// in this example the token is added to all usage of axios.
config.headers.Authorization = `Bearer ${keycloak.token}`
}
return config
}, error => {
console.error("tokenInterceptor: Rejected")
return Promise.reject(error)
})
}
*/
/*
app.use(VueKeyCloak,{
onReady: (keycloak) => {
console.log("Ready !")
tokenInterceptor()
},
init: {
onLoad: 'login-required',
checkLoginIframe: false,
},
config: {
realm: 'test',
url: 'http://localhost:7080',
clientId: 'myinpulse'
}
} );
*/
export { store };

View File

@ -0,0 +1,14 @@
// file: src/plugins/authStore.js
import { useAuthStore } from "@/stores/authStore.ts";
import keycloakService from '@/services/keycloak';
// Setup auth store as a plugin so it can be accessed globally in our FE
const authStorePlugin = {
install(app: any, option: any) {
const store = useAuthStore(option.pinia);
app.config.globalProperties.$store = store;
keycloakService.CallInitStore(store);
}
}
export default authStorePlugin;

View File

@ -4,14 +4,37 @@ const router = createRouter({
history: createWebHistory(import.meta.env.BASE_URL),
routes: [
{
path: "/test",
name: "test",
path: '/test',
name: 'test',
// route level code-splitting
// this generates a separate chunk (About.[hash].js) for this route
// which is lazy-loaded when the route is visited.
component: () => import("../views/testComponent.vue"),
component: () => import('../views/testComponent.vue'),
},
{
path: '/login',
name: 'login',
component: () => import('../components/LoginComponent.vue'),
},
{
path: '/',
name: 'Admin-main',
component: () => import('../views/AdminMain.vue'),
},
// route pour les canvas (made by adnane), in fact the two vue apps are separated for now
{
path: '/canvas',
name: 'canvas',
component: () => import('../views/CanvasView.vue'),
},
{
path: '/signup',
name: 'signup',
component: () => import('../views/EntrepSignUp.vue'),
},
],
});
})
export default router;

View File

@ -65,4 +65,28 @@ function callApi(
);
}
export { callApi };
function postApi(
endpoint: string,
data: any,
onSuccessHandler?: (response: AxiosResponse) => void,
onErrorHandler?: (error: AxiosError) => void
): void {
axiosInstance
.post(endpoint, data)
.then(onSuccessHandler ?? defaultApiSuccessHandler)
.catch(onErrorHandler ?? defaultApiErrorHandler);
}
function deleteApi(
endpoint: string,
onSuccessHandler?: (response: AxiosResponse) => void,
onErrorHandler?: (error: AxiosError) => void
): void {
axiosInstance
.delete(endpoint)
.then(onSuccessHandler ?? defaultApiSuccessHandler)
.catch(onErrorHandler ?? defaultApiErrorHandler);
}
export { axiosInstance, callApi, postApi, deleteApi };

View File

@ -54,7 +54,7 @@ const useAuthStore = defineStore("storeAuth", {
async logout() {
try {
await keycloakService.CallLogout(
import.meta.env.VITE_APP_URL + "/test"
import.meta.env.VITE_APP_URL + "/login" //redirect to login page instead of test...
);
await this.clearUserData();
} catch (error) {

View File

@ -0,0 +1,139 @@
<template>
<Header />
<error-wrapper></error-wrapper>
<div id="container">
<div id="main">
<h3> Projet en cours </h3>
<ProjectComp
v-for="(project, index) in projects"
:key="index"
:project-name="project.name"
:list-name="project.members"
:project-link="project.link"
/>
<div id ="main">
<h3> Projet en attente </h3>
<PendingProjectComponent
v-for="( project, index) in pendingProjects"
:key="index"
:project-name="project.name"
:creation-date="project.creationDate"
/>
</div>
<AddProjectForm/>
</div>
<Agenda :project-r-d-v="rendezVous" />
</div>
</template>
<script setup lang="ts">
import { ref, onMounted } from "vue";
import { callApi } from "@/services/api";
import Header from "../components/HeaderComponent.vue";
import Agenda from "../components/Agenda.vue";
import ProjectComp from "../components/ProjectComponent.vue";
import PendingProjectComponent from "@/components/PendingProjectComponent.vue";
import AddProjectForm from "@/components/AddProjectForm.vue";
const PORT = "8081";
const URI = `http://localhost:${PORT}`;
//const projects = ref<{ name: string; link: string; members: string[] }[]>([]);
/* const fetchProjects = () => {
callApi(
`${URI}/admin/projects`,
async (response) => {
console.log(response);
const projectList = response.data;
const projectPromises = projectList.map((project: any) => {
return new Promise(async (resolve) => {
callApi(
`${URI}/shared/projects/entrepreneurs/${project.idProject}`,
(memberResponse) => {
const members = memberResponse.data.map((m: any) => m.userName);
resolve({
name: project.projectName,
link: `/project/${project.idProject}`,
members,
});
},
() => {
// Error fetching members, still resolve with empty members
resolve({
name: project.projectName,
link: `/project/${project.idProject}`,
members: [],
});
}
);
});
});
projects.value = await Promise.all(projectPromises);
},
(error) => {
console.error("Error fetching projects:", error);
}
);
};
onMounted(fetchProjects);
*/
const projects = ref([
{
name: "Projet Alpha",
link: "/canvas", // to test
members: ["Alice", "Bob", "Charlie"],
},
{
name: "Projet Beta",
link: "./canvas", // to test
members: ["David", "Eve", "Frank"],
},
]);
const pendingProjects = ref ([
{ name: "l'eau", creationDate: "26-02-2024" },
{ name: "l'air", creationDate: "09-03-2023" },
])
const rendezVous = ref([
{ projectName: "Projet Alpha", date: "2025-03-10", lieu: "P106" },
{ projectName: "Projet Beta", date: "2025-04-15", lieu: "Td10" },
]);
</script>
<style scoped>
#container {
margin: 0;
display: grid;
grid-template-columns: 3fr 1fr; /* Main body takes 3/4, agenda 1/4 */
height: 100vh; /* Full viewport height */
}
button {
padding: 10px 15px;
background-color: #007bff;
color: white;
border: none;
cursor: pointer;
border-radius: 5px;
}
button:hover {
background-color: #0056b3;
}
</style>

View File

@ -0,0 +1,126 @@
<template>
<div>
<header>
<HeaderCanvas :project-id="1" />
</header>
</div>
<div>
<h1 class="page-title">PAGE CANVAS</h1>
<p class="canvas-help-text">
Cliquez sur un champ du tableau pour afficher son contenu en détail ci-dessous.
</p>
<LeanCanvas :is_admin=is_admin />
<div class="info-box">
<p>
Responsable : <strong>{{ admin.userName }} {{ admin.userSurname }}</strong><br />
Contact : <a href="mailto:{{ admin.primaryMail }}">{{ admin.primaryMail }}</a> |
<a href="tel:{{ admin.phoneNumber }}">{{ admin.phoneNumber }}</a>
</p>
</div>
</div>
</template>
<script setup lang="ts">
import HeaderCanvas from "../components/canvas/HeaderCanvas.vue";
import LeanCanvas from '../components/canvas/LeanCanvas.vue';
import { ref, onMounted, defineProps} from "vue";
import { axiosInstance } from "@/services/api.ts";
const IS_MOCK_MODE = true;
/*
const props = defineProps<{
projectId: number;
token: TokenPayload;
}>();
is_admin = token.includes("MyINPulse-admin")
*/
const is_admin = 0
// Variables pour les informations de l'administrateur
const admin = ref({
idUser: 0,
userSurname: "",
userName: "",
primaryMail: "",
secondaryMail: "",
phoneNumber: ""
});
const mockAdminData = {
idUser: 1,
userSurname: "ALAMI",
userName: "Adnane",
primaryMail: "mock.admin@example.com",
secondaryMail: "admin.backup@example.com",
phoneNumber: "0600000000"
};
// Fonction pour récupérer les données de l'administrateur
const fetchAdminData = async (projectId: number, useMock = IS_MOCK_MODE) => {
try {
if (useMock) {
console.log("Utilisation des données mockées pour l'administrateur");
admin.value = mockAdminData;
return;
}
const response = await axiosInstance.get(`/shared/projects/admin/${projectId}`);
admin.value = response.data;
} catch (error) {
console.error("Erreur lors de la récupération des données de l'administrateur :", error);
}
};
// Appeler la fonction fetch au montage du composant
onMounted(() => {
const projectId = 1;
fetchAdminData(projectId);
});
</script>
<style scoped>
.page-title {
text-align: center;
font-size: 2.5rem;
margin-top: 20px;
}
.canvas-help-text {
text-align: center;
font-size: 0.7rem;
color: #666;
}
.info-box {
background-color: #f9f9f9;
padding: 15px;
border-radius: 8px;
box-shadow: 0 2px 5px rgba(0, 0, 0, 0.1);
font-family: Arial, sans-serif;
width: 30%;
max-width: 600px;
margin: 20px auto;
}
.info-box p {
font-size: 16px;
line-height: 1.5;
color: #333;
}
.info-box a {
color: #007bff;
text-decoration: none;
}
.info-box a:hover {
text-decoration: underline;
}
</style>

View File

@ -0,0 +1,190 @@
<template>
<form class="add-project-form" @submit.prevent="submitForm">
<h2>Ajouter un projet</h2>
<div class="form-group">
<label for="name">Nom du projet</label>
<input
id="name"
v-model="form.name"
type="text"
required
/>
</div>
<h3>Entrepreneur</h3>
<div class="form-group">
<label for="founderName">Nom</label>
<input
id="founderName"
v-model="form.founder.userName"
type="text"
required
/>
</div>
<div class="form-group">
<label for="founderSurname">Prénom</label>
<input
id="founderSurname"
v-model="form.founder.userSurname"
type="text"
required
/>
</div>
<div class="form-group">
<label for="founderPrimaryMail">Email Principal</label>
<input
id="founderPrimaryMail"
v-model="form.founder.primaryMail"
type="email"
required
/>
</div>
<div class="form-group">
<label for="founderSecondaryMail">Email Secondaire</label>
<input
id="founderSecondaryMail"
v-model="form.founder.secondaryMail"
type="email"
/>
</div>
<div class="form-group">
<label for="founderPhoneNumber">Numéro de téléphone</label>
<input
id="founderPhoneNumber"
v-model="form.founder.phoneNumber"
type="tel"
required
/>
</div>
<div class="form-group">
<label for="founderSchool">École</label>
<input
id="founderSchool"
v-model="form.founder.school"
type="text"
required
/>
</div>
<div class="form-group">
<label for="founderCourse">Département</label>
<input
id="founderCourse"
v-model="form.founder.course"
type="text"
required
/>
</div>
<div class="form-group">
<label for="founderSneeStatus">Statut étudiant entrepreneur</label>
<input
id="founderSneeStatus"
v-model="form.founder.sneeStatus"
type="checkbox"
/>
</div>
<button type="submit">Soumettre</button>
</form>
</template>
<script setup lang="ts">
import { ref } from "vue";
import { postApi } from "@/services/api";
const form = ref({
name: '',
founder: {
userSurname: '',
userName: '',
primaryMail: '',
secondaryMail: '',
phoneNumber: '',
school: '',
course: '',
sneeStatus: false
}
});
function submitForm() {
postApi("/entrepreneur/projects/request", form.value);
}
</script>
<style scoped>
@import url('https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600&display=swap');
.add-project-form {
font-family: 'Inter', sans-serif;
max-width: 600px;
margin: 0 auto;
padding: 20px;
background: #fff;
border-radius: 10px;
box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1);
}
/* Le reste reste inchangé */
h2 {
margin-bottom: 20px;
font-size: 24px;
color: #333;
}
h3 {
margin-top: 20px;
font-size: 20px;
color: #555;
}
.form-group {
margin-bottom: 15px;
display: flex;
flex-direction: column;
}
label {
font-weight: 500;
margin-bottom: 5px;
}
input {
padding: 8px;
border-radius: 5px;
border: 1px solid #ccc;
font-size: 1em;
}
input[type="checkbox"] {
width: auto;
margin-right: 10px;
}
button {
background-color: #4caf50;
color: white;
padding: 10px 15px;
border: none;
border-radius: 5px;
cursor: pointer;
font-size: 1em;
width: 100%;
font-weight: 500;
}
button:hover {
background-color: #45a049;
}
button:active {
background-color: #388e3c;
}
input[type="text"]:focus,
input[type="email"]:focus,
input[type="tel"]:focus {
border-color: #4CAF50;
outline: none;
}
</style>

View File

@ -15,11 +15,13 @@ import ErrorModal from "@/components/errorModal.vue";
</template>
<style scoped>
.error-wrapper {
.error-wrapper{
position: absolute;
left: 70%;
//background-color: blue;
/*background-color: blue;*/
height: 100%;
width: 30%;
}
</style>